Compare commits

..

No commits in common. "main" and "mejorar-detalles" have entirely different histories.

105 changed files with 4251 additions and 4740 deletions

Binary file not shown.

View File

@ -191,11 +191,6 @@
<artifactId>viritin</artifactId>
<version>2.8.22</version>
</dependency>
<dependency>
<groupId>io.overcoded</groupId>
<artifactId>panel-for-vaadin</artifactId>
<version>24.3.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>

Binary file not shown.

View File

@ -15,33 +15,30 @@ import java.util.Optional;
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Exam extends BaseEntity {
public class Assessment extends BaseEntity {
@ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
@JoinTable(name = "EXAM_QUESTIONS", joinColumns = @JoinColumn(name = "exam_id"),
@JoinTable(name = "ASSESSMENT_QUESTIONS", joinColumns = @JoinColumn(name = "assessment_id"),
inverseJoinColumns = @JoinColumn(name = "question_id"))
private List<Question> questions = new ArrayList<>();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "exam", cascade = {CascadeType.ALL})
@OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL})
private List<Submission> submissions = new ArrayList<>();
@ManyToOne
private Candidate candidate;
@ManyToOne
private Evaluation evaluation;
@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
@JoinColumn(name = "EXAM_ID")
private List<ExamEvent> examEvents = new ArrayList<>();
@JoinColumn(name = "ASSESSMENT_ID")
private List<AssessmentEvent> assessmentEvents = new ArrayList<>();
public Submission getCurrentSubmission() {
return submissions.getLast();
}
public Long getRemainingTimeSeconds() {
final Optional<Instant> started = examEvents.stream()
.filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(ExamEvent::getTimestamp)
final Optional<Instant> started = assessmentEvents.stream()
.filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.map(AssessmentEvent::getTimestamp)
.findFirst();
final Integer totalTimeMinutes = questions.stream()
@ -55,24 +52,24 @@ public class Exam extends BaseEntity {
}
public Instant getStartingTime() {
final Optional<Instant> started = examEvents.stream()
.filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(ExamEvent::getTimestamp)
final Optional<Instant> started = assessmentEvents.stream()
.filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.map(AssessmentEvent::getTimestamp)
.findFirst();
return started.orElse(null);
}
public boolean isCompleted() {
return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.COMPLETED)
.map(ExamEvent::getTimestamp)
return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.COMPLETED)
.map(AssessmentEvent::getTimestamp)
.findFirst()
.isPresent();
}
public boolean isStarted() {
return examEvents.stream().filter(e -> e.getStatus() == ExamStatus.STARTED)
.map(ExamEvent::getTimestamp)
return assessmentEvents.stream().filter(e -> e.getStatus() == AssessmentStatus.STARTED)
.map(AssessmentEvent::getTimestamp)
.findFirst()
.isPresent();
}

View File

@ -13,7 +13,7 @@ import java.time.Instant;
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class ExamEvent extends BaseEntity {
public class AssessmentEvent extends BaseEntity {
private Instant timestamp;
private ExamStatus status;
private AssessmentStatus status;
}

View File

@ -1,6 +1,6 @@
package com.primefactorsolutions.model;
public enum ExamStatus {
public enum AssessmentStatus {
CREATED,
STARTED,
COMPLETED

View File

@ -1,11 +1,7 @@
package com.primefactorsolutions.model;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import java.time.Instant;
import java.util.UUID;
@MappedSuperclass
@ -13,18 +9,22 @@ public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
@Getter
@Setter
private UUID id;
@Version
@Getter
private int version;
@Getter
@ColumnDefault("NOW()")
private Instant created;
@ColumnDefault("NOW()")
@Getter
private Instant updated;
public UUID getId() {
return id;
}
public void setId(final UUID id) {
this.id = id;
}
public int getVersion() {
return version;
}
@Override
public int hashCode() {
@ -44,14 +44,4 @@ public abstract class BaseEntity {
}
return super.equals(that);
}
@PrePersist
public void updateCreated() {
this.created = Instant.now();
}
@PreUpdate
public void updateUpdated() {
this.updated = Instant.now();
}
}

View File

@ -6,17 +6,17 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Candidate extends BaseEntity implements HasLabel {
public class Candidate extends BaseEntity {
@Column(unique = true)
private String email;
@Override
public String getLabel() {
return email;
}
@OneToMany(fetch = FetchType.EAGER, mappedBy = "candidate")
private List<Assessment> assessments;
}

View File

@ -1,4 +0,0 @@
package com.primefactorsolutions.model;
public record Certification(String title, Integer year) {
}

View File

@ -1,34 +1,36 @@
package com.primefactorsolutions.model;
public enum DocumentType {
CARNET_DE_IDENTIDAD,
RECIBOS_DE_PAGO,
CONTRATO_DE_TRABAJO,
CERTIFICADO_DE_TRABAJO,
All,
ID_CARD,
PAY_STUB,
PAY_SLIPS,
EMPLOYMENT_CONTRACT,
WORK_CERTIFICATES,
NDA,
MEMORÁNDUMS,
APROBACIÓN_DE_CONTRATO_MTEPS,
CERTIFICADO_DE_ANTECEDENTES,
EVALUACIÓN_PRE_EMPLEO,
FORMULARIO_DE_INSCRIPCIÓN_AL_SEGURO,
FORMULARIO_DE_CANCELACIÓN_DE_SEGURO,
TÍTULO_PROFESIONAL_1,
CERTIFICACIÓN_PROFESIONAL_1,
TÍTULO_PROFESIONAL_2,
CERTIFICACIÓN_PROFECIONAL_2,
TÍTULO_PROFESIONAL_3,
CERTIFICACIÓN_PROFECIONAL_3,
NORMATIVA_LABORAL_GENERAL,
NORMAS_DE_TRABAJO_REMOTO,
NORMAS_DE_SEGURIDAD,
INSTRUCTIVOS_DE_RECURSOS_HUMANOS,
MANUAL_DE_FUNCIONES_DE_ADMINISTRACIÓN,
MANUAL_DE_FUNCIONES_DE_INGENIERÍA,
LEY_GENERAL_DEL_TRABAJO,
DECRETOS_SUPREMOS,
RESOLUCIONES_O_DISPOSICIONES_REGLAMENTARIAS,
NORMATIVA_COMPLEMENTARIA,
LEY_GRAL_DE_HIGIENE_SALUD_SEGURIDAD_OCUPACIONAL_Y_BIENESTAR,
NORMATIVA_REGLAMENTARIA_PARA_DESARROLLO_DE_PASANTÍAS,
OTROS
MEMORANDUMS,
CONTRACT_APPROVAL_MTEPS,
BACKGROUND_CHECK_CERTIFICATE,
PRE_EMPLOYMENT_EVALUATION,
INSURANCE_REGISTRATION_FORM,
INSURANCE_CANCELLATION_FORM,
PROFESSIONAL_DEGREE_1,
PROFESSIONAL_CERTIFICATE_1,
PROFESSIONAL_DEGREE_2,
PROFESSIONAL_CERTIFICATE_2,
PROFESSIONAL_DEGREE_3,
PROFESSIONAL_CERTIFICATE_3,
GENERAL_LABOR_REGULATIONS,
REMOTE_WORK_GUIDELINES,
SAFETY_REGULATIONS,
HUMAN_RESOURCES_GUIDELINES,
ADMINISTRATION_FUNCTIONS_MANUAL,
ENGINEERING_FUNCTIONS_MANUAL,
GENERAL_LABOR_LAW,
SUPREME_DECREE,
REGULATORY_RESOLUTION,
COMPLEMENTARY_REGULATION,
HEALTH_SAFETY_LAW,
INTERNSHIP_RULES,
OTHER
}

View File

@ -1,27 +1,24 @@
package com.primefactorsolutions.model;
import com.google.common.collect.Lists;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Type;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Collection;
import java.util.List;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Employee extends BaseEntity implements UserDetails, HasLabel {
public class Employee extends BaseEntity implements UserDetails {
private String username;
@NotNull(message = "El nombre no puede estar vacío")
@ -30,7 +27,6 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
@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;
@ -45,9 +41,9 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
@Email(message = "El correo personal no tiene un formato válido")
private String personalEmail;
@Pattern(regexp = "^[0-9]+$", message = "El número de teléfono debe contener solo números")
private String phoneNumberProfessional;
private String phoneNumberProfesional;
@Email(message = "El correo profesional no tiene un formato válido")
private String professionalEmail;
private String profesionalEmail;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "El cargo solo debe contener letras")
private String position;
@ -68,21 +64,25 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
@Pattern(regexp = "^[a-zA-Z0-9]+$", message = "El CI debe contener solo letras y números")
private String ci;
private String issuedIn;
private String pTitle1;
private String pTitle2;
private String pTitle3;
@Type(JsonType.class)
@Column(columnDefinition = "json")
@ColumnDefault("JSON_ARRAY()")
private List<Certification> educationTitles = Lists.newArrayList();
@Type(JsonType.class)
@Column(columnDefinition = "json")
@ColumnDefault("JSON_ARRAY()")
private List<Certification> certifications = Lists.newArrayList();
private String pStudy1;
private String pStudy2;
private String pStudy3;
private String certification1;
private String certification2;
private String certification3;
private String certification4;
private String recognition;
private String achievements;
@Type(JsonType.class)
@Column(columnDefinition = "json")
@ColumnDefault("JSON_ARRAY()")
private List<Language> languages = Lists.newArrayList();
private String language1;
private String language1Level;
private String language2;
private String language2Level;
@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")
@ -92,16 +92,16 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
private LocalDate dateOfExit;
private String seniority;
private BigDecimal salaryTotal = BigDecimal.ZERO;
private BigDecimal salaryBasic = BigDecimal.ZERO;
private BigDecimal professionalBonus = BigDecimal.ZERO;
private BigDecimal tenureBonus = BigDecimal.ZERO;
@Pattern(regexp = "^[0-9]+(\\.[0-9]{1,2})?$", message = "El salario debe ser un número con hasta dos decimales")
private String salarytotal;
private String salaryBasic;
private String bonoProfesional;
private String antiguedad;
@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;
@Setter
private String customContractType;
private String gpss;
private String sss;
@Pattern(regexp = "^[a-zA-Z ]+$", message = "Los derechohabientes solo deben contener letras")
@ -115,7 +115,7 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Lists.newArrayList(new SimpleGrantedAuthority("ROLE_" + this.role.name()));
return Lists.newArrayList();
}
@Override
@ -148,31 +148,13 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
return true;
}
@Enumerated(EnumType.STRING)
private MaritalStatus maritalStatus;
@Enumerated(EnumType.STRING)
private Gender gender;
@Enumerated(EnumType.STRING)
private ContractType contractType;
@Size(max = 255, message = "El detalle del contrato no debe exceder 255 caracteres")
private String otherContractDetail;
@Enumerated(EnumType.STRING)
private Role role = Role.USER;
@Override
public String getLabel() {
return String.format("%s %s", firstName, lastName);
}
public enum Status {
ACTIVE,
INACTIVE
}
public enum Gender {
MALE,
FEMALE
}
@Enumerated(EnumType.STRING)
private MaritalStatus maritalStatus;
public enum MaritalStatus {
SINGLE,
@ -181,13 +163,27 @@ public class Employee extends BaseEntity implements UserDetails, HasLabel {
DIVORCED
}
@Enumerated(EnumType.STRING)
private Gender gender;
public enum Gender {
MALE,
FEMALE
}
@Enumerated(EnumType.STRING)
private ContractType contractType;
public enum ContractType {
CONTRATO_LABORAL,
CONTRATO_CIVIL_O_SERVICIOS,
CONTRATO_PLAZO_FIJO,
CONSULTORIA_INTERNA,
CONSULTORIA_EXTERNA,
CONTRATO_MIXTO,
MIXTO,
OTROS
}
@Size(max = 255, message = "El detalle del contrato no debe exceder 255 caracteres")
private String otherContractDetail;
}

View File

@ -1,8 +0,0 @@
package com.primefactorsolutions.model;
public enum EmployeePosition {
JUNIOR_DEV,
JUNIOR_QA,
SENIOR_DEV,
SENIOR_QA
}

View File

@ -1,30 +0,0 @@
package com.primefactorsolutions.model;
import com.google.common.collect.Lists;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.Type;
import java.util.List;
@Entity
@Data
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Evaluation extends BaseEntity {
@ManyToOne
private Candidate candidate;
private Integer points;
private EmployeePosition candidatePosition;
private Employee interviewer;
@Type(JsonType.class)
@Column(columnDefinition = "json")
@ColumnDefault("JSON_ARRAY()")
private List<SkillEvaluation> skillEvaluations = Lists.newArrayList();
@OneToMany(fetch = FetchType.EAGER, mappedBy = "evaluation")
private List<Exam> exams;
}

View File

@ -1,5 +0,0 @@
package com.primefactorsolutions.model;
public interface HasLabel {
String getLabel();
}

View File

@ -0,0 +1,126 @@
package com.primefactorsolutions.model;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class HoursWorked extends BaseEntity {
@ManyToOne
@JoinColumn(name = "employee_id", nullable = true)
private Employee employee;
@ManyToOne
@JoinColumn(name = "team_id", nullable = true)
private Team team;
private int weekNumber;
private LocalDate date;
private String actividad;
private double hours;
private double horaspendientes;
private double totalHours;
private String tareaEspecifica;
public String getTareaEspecifica() {
return tareaEspecifica;
}
public void setTareaEspecifica(final String tareaEspecifica) {
this.tareaEspecifica = tareaEspecifica;
}
public static double calculateTotalHours(final List<HoursWorked> activities) {
return activities.stream()
.mapToDouble(activity -> activity.hours)
.sum();
}
public static double calculatePendingHours(final List<HoursWorked> activities) {
double totalHoursWorked = calculateTotalHours(activities);
return Math.max(0, 40 - totalHoursWorked);
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(final Employee employee) {
this.employee = employee;
}
public int getWeekNumber() {
return weekNumber;
}
public void setWeekNumber(final int weekNumber) {
this.weekNumber = weekNumber;
}
public LocalDate getDate() {
return date;
}
public void setDate(final LocalDate date) {
this.date = date;
if (date != null) {
WeekFields weekFields = WeekFields.of(Locale.getDefault());
this.weekNumber = date.get(weekFields.weekOfWeekBasedYear());
}
}
public String getActividad() {
return actividad;
}
public void setActividad(final String actividad) {
this.actividad = actividad;
}
public double getHours() {
return hours;
}
public void setHours(final double hours) {
this.hours = hours;
}
public double getTotalHours() {
double total = this.getHours();
return totalHours + total;
}
public void setTotalHours(final double totalHours) {
this.totalHours = totalHours;
}
public Team getTeam() {
return team;
}
public void setTeam(final Team team) {
this.team = team;
}
public double getHoraspendientes() {
//double horasTrabajadas = this.getTotalHours() + this.getHorasTareasEspecificas();
return 40;
}
public void setHoraspendientes(final double horaspendientes) {
this.horaspendientes = horaspendientes;
}
}

View File

@ -1,4 +0,0 @@
package com.primefactorsolutions.model;
public record Language(String name, Proficiency proficiency) {
}

View File

@ -1,17 +0,0 @@
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<? extends Payload>[] payload() default {};
int value();
}

View File

@ -1,24 +0,0 @@
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<MinAge, LocalDate> {
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;
}
}

View File

@ -1,7 +0,0 @@
package com.primefactorsolutions.model;
public enum Proficiency {
BASIC,
ADVANCED,
FLUENT
}

View File

@ -1,4 +0,0 @@
package com.primefactorsolutions.model;
public record SkillEvaluation(SkillType skillType, SkillLevel level, String comments) {
}

View File

@ -1,8 +0,0 @@
package com.primefactorsolutions.model;
public enum SkillLevel {
NEEDS_IMPROVEMENT,
FAIR,
VERY_GOOD,
EXCELLENT
}

View File

@ -1,8 +0,0 @@
package com.primefactorsolutions.model;
public enum SkillType {
TECHNICAL_KNOWLEDGE,
ASSERTIVENESS,
COMPLETENESS_AND_CONCISENESS,
COMMUNICATION
}

View File

@ -1,5 +1,7 @@
package com.primefactorsolutions.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToOne;
@ -7,6 +9,9 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Type;
import java.util.Map;
@Entity
@Data
@ -18,12 +23,14 @@ public class Submission extends BaseEntity {
private Question question;
@Lob
private String text;
private String response;
private String output;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private Map<String, Object> results;
private SubmissionStatus submissionStatus;
@ManyToOne
private Exam exam;
private Assessment assessment;
}

View File

@ -10,4 +10,7 @@ public enum TimeOffRequestStatus {
VENCIDO,
SOLICITADO,
EN_REVISION,
COMPLETADO,
CANCELADO,
}

View File

@ -1,6 +1,7 @@
package com.primefactorsolutions.model;
public enum TimeOffRequestType {
TODOS,
AÑO_NUEVO,
LUNES_CARNAVAL,
MARTES_CARNAVAL,

View File

@ -0,0 +1,21 @@
package com.primefactorsolutions.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import java.util.List;
@Entity
@Data
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Timesheet extends BaseEntity {
@Type(JsonType.class)
@Column(columnDefinition = "json")
private List<TimesheetEntry> entries;
}

View File

@ -1,30 +1,13 @@
package com.primefactorsolutions.model;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDate;
import java.time.temporal.IsoFields;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TimesheetEntry extends BaseEntity {
private String task;
private String details;
private LocalDate date;
private double hours;
@ManyToOne
@JoinColumn(name = "employee_id")
private Employee employee;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
public int getWeekNumber() {
return date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
}
public class TimesheetEntry {
private TaskType taskType;
private int hours;
}

View File

@ -8,22 +8,20 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TimeOff extends BaseEntity {
public class Vacation extends BaseEntity {
@Enumerated(EnumType.STRING)
private TimeOffRequestType category;
private LocalDate date;
private Integer monthOfYear;
private Integer dayOfMonth;
private Double duration;
private Double expiration;
@Enumerated(EnumType.STRING)
private Type type;
public enum Type {
FIXED,
MOVABLE,

View File

@ -1,46 +0,0 @@
package com.primefactorsolutions.model;
import com.google.common.collect.Lists;
import java.time.LocalDate;
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;
public record Week(LocalDate from, LocalDate to) {
@Override
public String toString() {
final DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE;
return String.format("from %s to %s", formatter.format(from), formatter.format(to));
}
public static Week getCurrent() {
final int weekNumber = LocalDate.now().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
final LocalDate from = getFirstDayOfWeek(weekNumber);
return new Week(from, from.plusDays(6));
}
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();
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) {
return LocalDate
.now()
.with(WeekFields.of(Locale.US).getFirstDayOfWeek())
.with(WeekFields.of(Locale.US).weekOfWeekBasedYear(), weekNumber);
}
}

View File

@ -1,9 +1,9 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.Evaluation;
import com.primefactorsolutions.model.Assessment;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface EvaluationRepository extends JpaRepository<Evaluation, UUID> {
public interface AssessmentRepository extends JpaRepository<Assessment, UUID> {
}

View File

@ -11,6 +11,5 @@ public interface EmployeeRepository extends JpaRepository<Employee, UUID> {
Optional<Employee> findByUsername(String username);
Optional<Employee> findByPersonalEmail(String personalEmail);
List<Employee> findAllByTeamId(UUID teamId);
//Optional<Employee> findByTeamIdAndLeadManagerTrue(UUID teamId);
List<Employee> findByTeamName(String teamName);
}

View File

@ -1,9 +0,0 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.Exam;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface ExamRepository extends JpaRepository<Exam, UUID> {
}

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.HoursWorked;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public interface HoursWorkedRepository extends JpaRepository<HoursWorked, UUID> {
List<HoursWorked> findByWeekNumber(int weekNumber);
List<HoursWorked> findByDate(LocalDate date);
List<HoursWorked> findByEmployeeIdAndWeekNumber(UUID employeeId, int weekNumber);
}

View File

@ -1,14 +0,0 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.TimeOff;
import com.primefactorsolutions.model.TimeOffRequestType;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public interface TimeOffRepository extends JpaRepository<TimeOff, UUID> {
TimeOff findByCategory(TimeOffRequestType category);
List<TimeOff> findByDateBetween(LocalDate from, LocalDate to);
}

View File

@ -10,7 +10,6 @@ import java.util.Optional;
import java.util.UUID;
public interface TimeOffRequestRepository extends JpaRepository<TimeOffRequest, UUID> {
List<TimeOffRequest> findByOrderByUpdatedDesc();
List<TimeOffRequest> findByEmployeeId(UUID idEmployee);
Optional<TimeOffRequest> findByEmployeeIdAndState(UUID employeeId, TimeOffRequestStatus state);
List<TimeOffRequest> findByEmployeeIdAndCategory(UUID employeeId, TimeOffRequestType category);

View File

@ -1,12 +0,0 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.TimesheetEntry;
import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
public interface TimesheetEntryRepository extends JpaRepository<TimesheetEntry, UUID> {
List<TimesheetEntry> findByDateBetween(LocalDate from, LocalDate to);
}

View File

@ -0,0 +1,11 @@
package com.primefactorsolutions.repositories;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.Vacation;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.UUID;
public interface VacationRepository extends JpaRepository<Vacation, UUID> {
Vacation findByCategory(TimeOffRequestType category);
}

View File

@ -0,0 +1,199 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.repositories.AssessmentRepository;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@Slf4j
public class AssessmentService {
private final AssessmentRepository assessmentRepository;
private final EntityManager entityManager;
private final JavaMailSender emailSender;
public Assessment createOrUpdate(final Assessment assessment) {
final Assessment saved = assessmentRepository.save(assessment);
return saved;
}
public void sendEmail(final Assessment assessment) {
try {
final String evaluationLink = String.format("https://careers.primefactorsolutions.com/evaluation/%s",
assessment.getId());
final SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("no-reply@primefactorsolutions.com");
message.setBcc("no-reply@primefactorsolutions.com");
message.setTo(assessment.getCandidate().getEmail());
message.setSubject("PFS - Evaluacion Tecnica");
message.setText(String.format("Estimado candidato,\n\nGracias por su candidatura. En esta etapa del "
+ "proceso de seleccion, usted debe completar "
+ "una evaluacion tecnica de programacion en JAVA. La prueba tiene una duracion de 30 minutos y "
+ "puede completarla cuando tenga una buena "
+ "conexion de internet.\n\n"
+ "Haga click aca: " + evaluationLink + "\n\n"
+ "Exito!"));
emailSender.send(message);
log.info("Sent email to {}", assessment.getCandidate().getEmail());
} catch (Exception e) {
log.error("Error sending email to {}", assessment.getCandidate().getEmail(), e);
throw e;
}
}
public List<Assessment> getAssessments() {
return assessmentRepository.findAll();
}
public Assessment getAssessment(final UUID id) {
return assessmentRepository.findById(id).orElse(null);
}
public Assessment startAssessment(final UUID id) {
final Assessment assessment = assessmentRepository.findById(id).get();
if (assessment.isStarted()) {
return assessment;
}
assessment.getAssessmentEvents().add(new AssessmentEvent(Instant.now(), AssessmentStatus.STARTED));
return assessmentRepository.save(assessment);
}
public Submission getNextSubmission(final UUID assessmentId, final UUID currSubmissionId) {
return getNextSubmission(assessmentId, currSubmissionId, true);
}
public Submission getNextSubmission(final UUID assessmentId, final UUID currSubmissionId,
final boolean checkCompleted) {
final Assessment assessment = assessmentRepository.findById(assessmentId).get();
Assessment saved;
if (currSubmissionId == null) {
if (checkCompleted && assessment.isCompleted()) {
return null;
}
final Question firstQuestion = assessment.getQuestions().stream().findFirst().get();
final Optional<Submission> submissionToReturn = assessment.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(firstQuestion))
.findFirst();
if (submissionToReturn.isEmpty()) {
final Submission result = new Submission(firstQuestion, firstQuestion.getContent(), Map.of(),
SubmissionStatus.FAIL, assessment);
assessment.getSubmissions().add(result);
}
saved = assessmentRepository.save(assessment);
final Optional<Submission> submissionToReturn2 = saved.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(firstQuestion))
.findFirst();
return submissionToReturn2.get();
}
final Submission currSubmission = assessment.getSubmissions().stream()
.filter(s -> s.getId().equals(currSubmissionId))
.findFirst().get();
final Question currQuestion = currSubmission.getQuestion();
int idx = assessment.getQuestions().indexOf(currQuestion);
if (idx == assessment.getQuestions().size() - 1) {
return null;
}
final Question nextQuestion = assessment.getQuestions().get(idx + 1);
final Optional<Submission> submissionToReturn = assessment.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(nextQuestion))
.findFirst();
if (submissionToReturn.isEmpty()) {
final Submission result = new Submission(nextQuestion, nextQuestion.getContent(), Map.of(),
SubmissionStatus.FAIL, assessment);
assessment.getSubmissions().add(result);
}
saved = assessmentRepository.save(assessment);
final Optional<Submission> submissionToReturn2 = saved.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(nextQuestion))
.findFirst();
return submissionToReturn2.get();
}
public Submission getPrevSubmission(final UUID assessmentId, final Submission currSubmission) {
if (currSubmission == null) {
return null;
}
final Question currQuestion = currSubmission.getQuestion();
Assessment assessment = assessmentRepository.findById(assessmentId).get();
int idx = assessment.getQuestions().indexOf(currQuestion);
if (idx == 0) {
return null;
}
final Question prevQuestion = assessment.getQuestions().get(idx - 1);
return assessment.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(prevQuestion))
.findFirst().orElseThrow(() -> new IllegalStateException("submission invalid"));
}
public Assessment completeAssessment(final UUID id) {
Assessment assessment = assessmentRepository.findById(id).get();
Optional<AssessmentEvent> completed = assessment.getAssessmentEvents().stream()
.filter(e -> e.getStatus() == AssessmentStatus.COMPLETED)
.findFirst();
if (completed.isPresent()) {
return assessment;
}
assessment.getAssessmentEvents().add(new AssessmentEvent(Instant.now(), AssessmentStatus.COMPLETED));
Assessment saved = assessmentRepository.save(assessment);
return saved;
}
public void saveSubmission(final UUID id, final Submission currSubmission) {
Assessment assessment = assessmentRepository.findById(id).get();
final Submission submission = assessment.getSubmissions().stream()
.filter(s -> s.getId().equals(currSubmission.getId()))
.findFirst().get();
submission.setResponse(currSubmission.getResponse());
Assessment saved = assessmentRepository.save(assessment);
}
@Transactional
public Assessment saveAssessment(final Assessment assessment) {
Candidate merged = entityManager.merge(assessment.getCandidate());
List<Question> mergedQuestions = assessment.getQuestions().stream().map(entityManager::merge)
.collect(Collectors.toList());
assessment.setCandidate(merged);
assessment.setQuestions(mergedQuestions);
return assessmentRepository.save(assessment);
}
}

View File

@ -13,8 +13,8 @@ import java.util.UUID;
public class CandidateService {
private final CandidateRepository candidateRepository;
public Candidate createOrUpdate(final Candidate candidate) {
final Candidate saved = candidateRepository.save(candidate);
public Candidate createOrUpdate(final Candidate assessment) {
final Candidate saved = candidateRepository.save(assessment);
return saved;
}

View File

@ -38,10 +38,6 @@ public class EmployeeService {
.build();
}
public List<Employee> getEmployees() {
return employeeRepository.findAll();
}
public Employee getDetachedEmployeeByUsername(final String username) {
final Employee employee = employeeRepository.findByUsername(username).orElse(null);
@ -59,6 +55,7 @@ public class EmployeeService {
Optional<Employee> leadManager = teamMembers.stream()
.filter(e -> Strings.isNullOrEmpty(e.getLeadManager()))
.findFirst();
return leadManager.map(employee -> employee.getFirstName() + " " + employee.getLastName())
.orElse("No asignado");
}
@ -66,7 +63,6 @@ public class EmployeeService {
public List<Employee> findEmployees(
final int start, final int pageSize, final String sortProperty, final boolean asc) {
List<Employee> employees = employeeRepository.findAll();
Collections.reverse(employees);
int end = Math.min(start + pageSize, employees.size());
employees.sort(new BeanComparator<>(sortProperty));
@ -80,7 +76,6 @@ public class EmployeeService {
public List<Employee> findEmployees(final int start, final int pageSize) {
List<Employee> employees = employeeRepository.findAll();
Collections.reverse(employees);
int end = Math.min(start + pageSize, employees.size());
return employees.subList(start, end);
@ -130,9 +125,7 @@ public class EmployeeService {
}
public List<Employee> findAllEmployees() {
List<Employee> employees = employeeRepository.findAll();
Collections.reverse(employees);
return employees;
return employeeRepository.findAll();
}
public List<Employee> findEmployeesByTeam(final String teamName) {

View File

@ -1,27 +0,0 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.Evaluation;
import com.primefactorsolutions.repositories.EvaluationRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@AllArgsConstructor
public class EvaluationService {
private final EvaluationRepository evaluationRepository;
public Evaluation createOrUpdate(final Evaluation candidate) {
return evaluationRepository.save(candidate);
}
public List<Evaluation> getEvaluations() {
return evaluationRepository.findAll();
}
public Evaluation getEvaluation(final UUID id) {
return evaluationRepository.findById(id).orElse(null);
}
}

View File

@ -1,197 +0,0 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.repositories.ExamRepository;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@Slf4j
public class ExamService {
private final ExamRepository examRepository;
private final EntityManager entityManager;
private final JavaMailSender emailSender;
public Exam createOrUpdate(final Exam exam) {
return examRepository.save(exam);
}
public void sendEmail(final Exam exam) {
try {
final String evaluationLink = String.format("https://careers.primefactorsolutions.com/candidate-exam/%s",
exam.getId());
final SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("no-reply@primefactorsolutions.com");
message.setBcc("no-reply@primefactorsolutions.com");
message.setTo(exam.getCandidate().getEmail());
message.setSubject("PFS - Evaluacion Tecnica");
message.setText(String.format("Estimado candidato,\n\nGracias por su candidatura. En esta etapa del "
+ "proceso de seleccion, usted debe completar "
+ "una evaluacion tecnica de programacion en JAVA. La prueba tiene una duracion de 30 minutos y "
+ "puede completarla cuando tenga una buena "
+ "conexion de internet.\n\n"
+ "Haga click aca: " + evaluationLink + "\n\n"
+ "Exito!"));
emailSender.send(message);
log.info("Sent email to {}", exam.getCandidate().getEmail());
} catch (Exception e) {
log.error("Error sending email to {}", exam.getCandidate().getEmail(), e);
throw e;
}
}
public List<Exam> getExams() {
return examRepository.findAll();
}
public Exam getExam(final UUID id) {
return examRepository.findById(id).orElse(null);
}
public Exam startExam(final UUID id) {
final Exam exam = examRepository.findById(id).get();
if (exam.isStarted()) {
return exam;
}
exam.getExamEvents().add(new ExamEvent(Instant.now(), ExamStatus.STARTED));
return examRepository.save(exam);
}
public Submission getNextSubmission(final UUID examId, final UUID currSubmissionId) {
return getNextSubmission(examId, currSubmissionId, true);
}
public Submission getNextSubmission(final UUID examId, final UUID currSubmissionId,
final boolean checkCompleted) {
final Exam exam = examRepository.findById(examId).get();
Exam saved;
if (currSubmissionId == null) {
if (checkCompleted && exam.isCompleted()) {
return null;
}
final Question firstQuestion = exam.getQuestions().stream().findFirst().get();
final Optional<Submission> submissionToReturn = exam.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(firstQuestion))
.findFirst();
if (submissionToReturn.isEmpty()) {
final Submission result = new Submission(firstQuestion, firstQuestion.getContent(), null,
SubmissionStatus.FAIL, exam);
exam.getSubmissions().add(result);
}
saved = examRepository.save(exam);
final Optional<Submission> submissionToReturn2 = saved.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(firstQuestion))
.findFirst();
return submissionToReturn2.get();
}
final Submission currSubmission = exam.getSubmissions().stream()
.filter(s -> s.getId().equals(currSubmissionId))
.findFirst().get();
final Question currQuestion = currSubmission.getQuestion();
int idx = exam.getQuestions().indexOf(currQuestion);
if (idx == exam.getQuestions().size() - 1) {
return null;
}
final Question nextQuestion = exam.getQuestions().get(idx + 1);
final Optional<Submission> submissionToReturn = exam.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(nextQuestion))
.findFirst();
if (submissionToReturn.isEmpty()) {
final Submission result = new Submission(nextQuestion, nextQuestion.getContent(), null,
SubmissionStatus.FAIL, exam);
exam.getSubmissions().add(result);
}
saved = examRepository.save(exam);
final Optional<Submission> submissionToReturn2 = saved.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(nextQuestion))
.findFirst();
return submissionToReturn2.get();
}
public Submission getPrevSubmission(final UUID examId, final Submission currSubmission) {
if (currSubmission == null) {
return null;
}
final Question currQuestion = currSubmission.getQuestion();
Exam exam = examRepository.findById(examId).get();
int idx = exam.getQuestions().indexOf(currQuestion);
if (idx == 0) {
return null;
}
final Question prevQuestion = exam.getQuestions().get(idx - 1);
return exam.getSubmissions().stream()
.filter(s -> s.getQuestion().equals(prevQuestion))
.findFirst().orElseThrow(() -> new IllegalStateException("submission invalid"));
}
public Exam completeExam(final UUID id) {
Exam exam = examRepository.findById(id).get();
Optional<ExamEvent> completed = exam.getExamEvents().stream()
.filter(e -> e.getStatus() == ExamStatus.COMPLETED)
.findFirst();
if (completed.isPresent()) {
return exam;
}
exam.getExamEvents().add(new ExamEvent(Instant.now(), ExamStatus.COMPLETED));
Exam saved = examRepository.save(exam);
return saved;
}
public void saveSubmission(final UUID id, final Submission currSubmission) {
Exam exam = examRepository.findById(id).get();
final Submission submission = exam.getSubmissions().stream()
.filter(s -> s.getId().equals(currSubmission.getId()))
.findFirst().get();
submission.setText(currSubmission.getText());
Exam saved = examRepository.save(exam);
}
@Transactional
public Exam saveExam(final Exam exam) {
Candidate merged = entityManager.merge(exam.getCandidate());
List<Question> mergedQuestions = exam.getQuestions().stream().map(entityManager::merge)
.collect(Collectors.toList());
exam.setCandidate(merged);
exam.setQuestions(mergedQuestions);
return examRepository.save(exam);
}
}

View File

@ -0,0 +1,99 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import org.apache.commons.beanutils.BeanComparator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.*;
@Service
public class HoursWorkedService {
private final HoursWorkedRepository hoursWorkedRepository;
@Autowired
public HoursWorkedService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
public List<HoursWorked> findAll() {
return hoursWorkedRepository.findAll();
}
public double getTotalHoursWorkedByEmployeeForWeek(final UUID employeeId, final int weekNumber) {
List<HoursWorked> hoursWorkedList = hoursWorkedRepository.findByWeekNumber(weekNumber);
return hoursWorkedList.stream()
.filter(hw -> hw.getEmployee().getId().equals(employeeId))
.mapToDouble(HoursWorked::getTotalHours)
.sum();
}
public HoursWorked findHoursWorked(final UUID id) {
Optional<HoursWorked> hoursWorked = hoursWorkedRepository.findById(id);
HoursWorked hw = hoursWorked.get();
return hw;
}
public HoursWorked saveHoursWorked(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public HoursWorked save(final HoursWorked hoursWorked) {
return hoursWorkedRepository.save(hoursWorked);
}
public double getTotalHoursForEmployee(final UUID employeeId, final int weekNumber) {
List<HoursWorked> activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
return HoursWorked.calculateTotalHours(activities);
}
public double getPendingHoursForEmployee(final UUID employeeId, final int weekNumber) {
List<HoursWorked> activities = hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
return HoursWorked.calculatePendingHours(activities);
}
public List<HoursWorked> findByWeekNumber(final int weekNumber) {
return hoursWorkedRepository.findByWeekNumber(weekNumber);
}
public List<HoursWorked> findByDate(final LocalDate date) {
return hoursWorkedRepository.findByDate(date);
}
public List<HoursWorked> findByDateAndWeekNumber(final LocalDate date, final int weekNumber) {
return hoursWorkedRepository.findByDate(date);
}
public List<HoursWorked> findHoursWorkeds(
final int start, final int pageSize, final String sortProperty, final boolean asc) {
List<HoursWorked> hoursWorkeds = hoursWorkedRepository.findAll();
int end = Math.min(start + pageSize, hoursWorkeds.size());
hoursWorkeds.sort(new BeanComparator<>(sortProperty));
if (!asc) {
Collections.reverse(hoursWorkeds);
}
return hoursWorkeds.subList(start, end);
}
public List<HoursWorked> findHoursWorkeds(final int start, final int pageSize) {
List<HoursWorked> hoursWorkeds = hoursWorkedRepository.findAll();
int end = Math.min(start + pageSize, hoursWorkeds.size());
return hoursWorkeds.subList(start, end);
}
public HoursWorked getHoursWorked(final UUID id) {
final Optional<HoursWorked> hoursWorked = hoursWorkedRepository.findById(id);
return hoursWorked.get();
}
public List<HoursWorked> findListHoursWorkedEmployee(final UUID employeeId, final int weekNumber) {
return hoursWorkedRepository.findByEmployeeIdAndWeekNumber(employeeId, weekNumber);
}
}

View File

@ -2,7 +2,7 @@ package com.primefactorsolutions.service;
import com.openhtmltopdf.pdfboxout.PdfBoxRenderer;
import com.openhtmltopdf.pdfboxout.PdfRendererBuilder;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.repositories.HoursWorkedRepository;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
@ -24,9 +24,13 @@ import java.util.TimeZone;
@Service
public class ReportService {
public ReportService() {
private final HoursWorkedRepository hoursWorkedRepository;
public ReportService(final HoursWorkedRepository hoursWorkedRepository) {
this.hoursWorkedRepository = hoursWorkedRepository;
}
// Este método ahora solo crea el archivo Excel a partir de los datos que recibe.
public byte[] writeAsExcel(final String reportName, final List<String> headers,
final List<Map<String, Object>> data, final String selectedTeam,
final int weekNumber, final int currentYear)
@ -170,81 +174,4 @@ public class ReportService {
return cfg;
}
public byte[] generateExcelReport(final Employee employee) {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("Empleado");
Row titleRow = sheet.createRow(0);
Cell titleCell = titleRow.createCell(0);
titleCell.setCellValue("Información General del Empleado");
CellStyle titleStyle = workbook.createCellStyle();
Font titleFont = workbook.createFont();
titleFont.setBold(true);
titleFont.setFontHeightInPoints((short) 16);
titleStyle.setFont(titleFont);
titleStyle.setAlignment(HorizontalAlignment.CENTER);
titleCell.setCellStyle(titleStyle);
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 40));
Row header = sheet.createRow(2);
String[] headers = {
"Username", "Nombres", "Apellidos", "Estado", "Género", "Fecha de Nacimiento", "Edad",
"Ciudad y País de Nacimiento", "Dirección de Residencia", "Departamento y Provincia de Residencia",
"Estado Civil", "Número de Hijos", "CI", "Expedido en", "Teléfono", "E-mail Personal",
"Teléfono Laboral",
"E-mail Laboral", "Nombres y Apellidos de Contacto", "Dirección de Contacto",
"Teléfono de Contacto",
"Email de Contacto", "Código de Empleado", "Cargo", "Equipo", "Lead/Manager", "Fecha de Ingreso",
"Fecha de Retiro", "Tipo de Contrato", "Tipo de Contrato Personalizado", "Antigüedad",
"Salario Total",
"Salario Básico", "Bono de Antigüedad", "Bono Profesional", "Banco", "Número de Cuenta",
"Código Único de Asegurado (GPSS)", "Matrícula de Asegurado (SSS)", "Derechohabiente 1",
"Derechohabiente 2"
};
CellStyle headerStyle = workbook.createCellStyle();
Font headerFont = workbook.createFont();
headerFont.setBold(true);
headerStyle.setFont(headerFont);
headerStyle.setAlignment(HorizontalAlignment.CENTER);
headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);
for (int i = 0; i < headers.length; i++) {
Cell cell = header.createCell(i);
cell.setCellValue(headers[i]);
cell.setCellStyle(headerStyle);
}
Row dataRow = sheet.createRow(3); // Fila 3
String[] employeeData = {
employee.getUsername(), employee.getFirstName(), employee.getLastName(),
employee.getStatus().toString(),
employee.getGender().toString(), employee.getBirthday().toString(),
String.valueOf(employee.getAge()),
employee.getBirthCity(), employee.getResidenceAddress(), employee.getLocalAddress(),
employee.getMaritalStatus().toString(), String.valueOf(employee.getNumberOfChildren()),
employee.getCi(),
employee.getIssuedIn(), employee.getPhoneNumber(), employee.getPersonalEmail(),
employee.getPhoneNumberProfessional(), employee.getProfessionalEmail(),
employee.getEmergencyCName(),
employee.getEmergencyCAddress(), employee.getEmergencyCPhone(), employee.getEmergencyCEmail(),
employee.getCod(), employee.getPosition(), employee.getTeam().getName(), employee.getLeadManager(),
employee.getDateOfEntry().toString(), employee.getDateOfExit() != null ? employee.getDateOfExit()
.toString() : "",
employee.getContractType().toString(), employee.getCustomContractType(),
employee.getSeniority(), employee.getSalaryTotal().toString(), employee.getSalaryBasic().toString(),
employee.getTenureBonus().toString(), employee.getProfessionalBonus().toString(),
employee.getBankName(), employee.getAccountNumber(), employee.getGpss(), employee.getSss(),
employee.getBeneficiarie1(), employee.getBeneficiarie2()
};
for (int i = 0; i < employeeData.length; i++) {
dataRow.createCell(i).setCellValue(employeeData[i] != null ? employeeData[i] : "");
}
for (int i = 0; i < headers.length; i++) {
sheet.autoSizeColumn(i);
}
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
workbook.write(outputStream);
return outputStream.toByteArray();
}
} catch (IOException e) {
throw new RuntimeException("Error al generar el reporte Excel", e);
}
}
}

View File

@ -32,7 +32,7 @@ public class TimeOffRequestService {
}
public List<TimeOffRequest> findAllTimeOffRequests() {
return timeOffRequestRepository.findByOrderByUpdatedDesc();
return timeOffRequestRepository.findAll();
}
public TimeOffRequest findTimeOffRequest(final UUID id) {

View File

@ -1,39 +0,0 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.TimeOff;
import com.primefactorsolutions.repositories.TimeOffRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
@Service
@AllArgsConstructor
public class TimeOffService {
private final TimeOffRepository timeOffRepository;
public TimeOff getTimeOff(final UUID id) {
return timeOffRepository.findById(id).orElse(null);
}
public void saveTimeOff(final TimeOff timeOff) {
timeOffRepository.save(timeOff);
}
public TimeOff findVacationByCategory(final TimeOffRequestType category) {
return timeOffRepository.findByCategory(category);
}
public List<TimeOff> findVacations() {
return timeOffRepository.findAll();
}
public List<TimeOff> findTimeOffs(final Integer year) {
final LocalDate from = LocalDate.of(year, 1, 1);
final LocalDate to = LocalDate.of(year, 12, 31);
return timeOffRepository.findByDateBetween(from, to);
}
}

View File

@ -1,37 +0,0 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.TimesheetEntry;
import com.primefactorsolutions.repositories.TimesheetEntryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.*;
@Service
public class TimesheetService {
private final TimesheetEntryRepository timesheetEntryRepository;
@Autowired
public TimesheetService(final TimesheetEntryRepository timesheetEntryRepository) {
this.timesheetEntryRepository = timesheetEntryRepository;
}
public List<TimesheetEntry> findAll() {
return timesheetEntryRepository.findAll();
}
public TimesheetEntry save(final TimesheetEntry timesheetEntry) {
return timesheetEntryRepository.save(timesheetEntry);
}
public TimesheetEntry getTimesheetEntry(final UUID id) {
final Optional<TimesheetEntry> hoursWorked = timesheetEntryRepository.findById(id);
return hoursWorked.orElse(null);
}
public List<TimesheetEntry> findListHoursWorkedEmployee(final LocalDate from,
final LocalDate to) {
return timesheetEntryRepository.findByDateBetween(from, to);
}
}

View File

@ -0,0 +1,23 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.TimeOffRequestType;
import com.primefactorsolutions.model.Vacation;
import com.primefactorsolutions.repositories.VacationRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@AllArgsConstructor
public class VacationService {
private final VacationRepository vacationRepository;
public Vacation findVacationByCategory(final TimeOffRequestType category) {
return vacationRepository.findByCategory(category);
}
public List<Vacation> findVacations() {
return vacationRepository.findAll();
}
}

View File

@ -1,13 +1,11 @@
package com.primefactorsolutions.views.assessment;
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.AssessmentService;
import com.primefactorsolutions.service.QuestionService;
import com.primefactorsolutions.service.CandidateService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.combobox.ComboBox;
@ -17,34 +15,32 @@ import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.fields.SubListSelector;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@RolesAllowed("ROLE_ADMIN")
@PermitAll
@Scope("prototype")
@PageTitle("Exams")
@Route(value = "/exams", layout = MainLayout.class)
@PageTitle("Assessments")
@Route(value = "/assessments", layout = MainLayout.class)
@Uses(ComboBox.class)
public class ExamView extends BaseEntityForm<Exam> implements HasUrlParameter<String> {
private final ExamService examService;
public class AssessmentView extends BeanValidationForm<Assessment> implements HasUrlParameter<String> {
private final AssessmentService assessmentService;
private final ComboBox<Candidate> candidate;
private final SubListSelector<Question> questions;
private ComboBox<Candidate> candidate = null;
private SubListSelector<Question> questions = null;
public ExamView(final AuthenticationContext authorizationContext,
final ExamService examService,
final QuestionService questionService,
final CandidateService candidateService) {
super(authorizationContext, Exam.class);
public AssessmentView(final AssessmentService assessmentService, final QuestionService questionService,
final CandidateService candidateService) {
super(Assessment.class);
this.examService = examService;
this.assessmentService = assessmentService;
candidate = new ComboBox<>("Candidate", candidateService.getCandidates());
candidate.setItemLabelGenerator((ItemLabelGenerator<Candidate>) Candidate::getEmail);
@ -55,20 +51,20 @@ public class ExamView extends BaseEntityForm<Exam> implements HasUrlParameter<St
questions.setReadOnly(false);
questions.setAvailableOptions(questionService.getQuestions());
setSavedHandler((SavedHandler<Exam>) exam -> {
this.examService.saveExam(exam);
goTo(ExamsListView.class);
setSavedHandler((SavedHandler<Assessment>) assessment -> {
final var saved = this.assessmentService.saveAssessment(assessment);
setEntityWithEnabledSave(saved);
});
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
final var exam = examService.getExam(UUID.fromString(s));
final var assessment = assessmentService.getAssessment(UUID.fromString(s));
setEntityWithEnabledSave(exam);
setEntityWithEnabledSave(assessment);
} else {
setEntityWithEnabledSave(new Exam());
setEntityWithEnabledSave(new Assessment());
}
}

View File

@ -0,0 +1,127 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.service.AssessmentService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.addon.stefan.clipboard.ClientsideClipboard;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Assessments")
@Route(value = "/assessments", layout = MainLayout.class)
@PermitAll
public class AssessmentsListView extends BaseView {
public AssessmentsListView(final AssessmentService assessmentService) {
final HorizontalLayout hl = new HorizontalLayout();
final Button addAssessment = new Button("Add Assessment");
addAssessment.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().flatMap(ui -> ui.navigate(AssessmentView.class, "new"));
});
hl.add(addAssessment);
final VGrid<Assessment> grid = new VGrid<>(Assessment.class);
grid.setColumns("id", "candidate.email");
final Grid.Column<Assessment> statusColumn = grid.addColumn((ValueProvider<Assessment, Object>) assessment ->
assessment.getAssessmentEvents().isEmpty()
? "N/A"
: assessment.getAssessmentEvents().getLast().getStatus().name());
statusColumn.setHeader("Status");
grid.addComponentColumn((ValueProvider<Assessment, Component>) assessment -> {
var result = new Button("Result", event ->
this.getUI().flatMap(ui -> ui.navigate(SubmissionView.class, assessment.getId().toString()))
);
result.setEnabled(assessment.isCompleted());
return result;
});
grid.addComponentColumn((ValueProvider<Assessment, Component>) assessment -> new Button("Copy Link", event ->
ClientsideClipboard.writeToClipboard(
String.format("email: %s link: https://careers.primefactorsolutions.com/evaluation/%s",
assessment.getCandidate().getEmail(),
assessment.getId()))
));
grid.addComponentColumn((ValueProvider<Assessment, Component>) assessment ->
new Button("Send Email", event -> {
ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Send Link Email");
dialog.setText(String.format("Enviar link por email al candidato %s?",
assessment.getCandidate().getEmail()));
dialog.setCancelable(true);
dialog.setConfirmText("Enviar");
dialog.setConfirmButtonTheme("primary");
dialog.addConfirmListener((ComponentEventListener<ConfirmDialog.ConfirmEvent>) confirmEvent -> {
try {
assessmentService.sendEmail(assessment);
} catch (Exception e) {
Notification.show("Error sending email: " + e.getMessage(), 10_000,
Notification.Position.TOP_CENTER);
}
});
dialog.open();
}));
grid.setDataProvider(new DataProvider<>() {
@Override
public boolean isInMemory() {
return false;
}
@Override
public int size(final Query<Assessment, Object> query) {
return assessmentService.getAssessments().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Assessment> fetch(final Query<Assessment, Object> query) {
int limit = query.getLimit();
int pagerSize = query.getPageSize();
int page = query.getPage();
return assessmentService.getAssessments().stream();
}
@Override
public void refreshItem(final Assessment assessment) {
// no-op
}
@Override
public void refreshAll() {
}
@Override
public Registration addDataProviderListener(final DataProviderListener<Assessment> dataProviderListener) {
return null;
}
});
grid.setAllRowsVisible(true);
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,76 +0,0 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.views.util.AuthUtils;
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.orderedlayout.HorizontalLayout;
import com.vaadin.flow.spring.security.AuthenticationContext;
import org.vaadin.firitin.form.BeanValidationForm;
import java.io.Serializable;
import java.util.Optional;
import java.util.UUID;
@SuppressWarnings("deprecation")
public abstract class BaseEntityForm<T> extends BeanValidationForm<T> {
private final AuthenticationContext authenticationContext;
private Button editButton;
private EditHandler<T> editHandler;
public BaseEntityForm(final AuthenticationContext authenticationContext, final Class<T> entityType) {
super(entityType);
this.authenticationContext = authenticationContext;
}
@Override
public HorizontalLayout getToolbar() {
return new HorizontalLayout(getCancelButton(), getEditButton(), getSaveButton());
}
public Button getEditButton() {
if (editButton == null) {
editButton = new Button("Edit");
editButton.setEnabled(false);
editButton.addClickListener(__ -> {
if (editHandler != null) {
editHandler.onEdit(getEntity());
}
});
}
return editButton;
}
public void setEditHandler(final EditHandler<T> editHandler) {
this.editHandler = editHandler;
getEditButton().setEnabled(editHandler != null);
}
protected <V extends Component> void goTo(final Class<V> view) {
getUI().ifPresent(ui -> ui.navigate(view));
}
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);
}
public interface EditHandler<T> extends Serializable {
void onEdit(T entity);
}
}

View File

@ -1,31 +1,16 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.views.util.AuthUtils;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.spring.security.AuthenticationContext;
import lombok.Getter;
import java.util.Optional;
import java.util.UUID;
@Getter
public abstract class BaseView extends Main {
public class BaseView extends Main {
private final VerticalLayout currentPageLayout;
private final AuthenticationContext authenticationContext;
public BaseView(final AuthenticationContext authenticationContext) {
this.authenticationContext = authenticationContext;
public BaseView() {
currentPageLayout = new VerticalLayout();
add(currentPageLayout);
}
protected boolean isRoleAdmin() {
return AuthUtils.isAdmin(this.authenticationContext);
}
protected Optional<UUID> getEmployeeId() {
return AuthUtils.getEmployeeId(this.authenticationContext);
}
}

View File

@ -0,0 +1,4 @@
package com.primefactorsolutions.views;
public class CalendarView {
}

View File

@ -1,9 +1,7 @@
package com.primefactorsolutions.views.assessment;
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.service.CandidateService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.router.BeforeEvent;
@ -11,35 +9,34 @@ import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Candidates")
@PageTitle("Assessments")
@Route(value = "/candidates", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class CandidateView extends BaseEntityForm<Candidate> implements HasUrlParameter<String> {
@PermitAll
public class CandidateView extends BeanValidationForm<Candidate> implements HasUrlParameter<String> {
private final CandidateService candidateService;
private EmailField email = null;
public CandidateView(final AuthenticationContext authenticationContext,
final CandidateService candidateService) {
super(authenticationContext, Candidate.class);
public CandidateView(final CandidateService candidateService) {
super(Candidate.class);
this.candidateService = candidateService;
email = new EmailField();
email.setWidthFull();
email.setLabel("Email");
setSavedHandler((SavedHandler<Candidate>) candidate -> {
candidateService.createOrUpdate(candidate);
goTo(CandidatesListView.class);
final Candidate saved = candidateService.createOrUpdate(candidate);
setEntityWithEnabledSave(saved);
});
}

View File

@ -0,0 +1,88 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.service.CandidateService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Candidates")
@Route(value = "/candidates", layout = MainLayout.class)
@PermitAll
public class CandidatesListView extends BaseView {
public CandidatesListView(final CandidateService candidateService) {
final HorizontalLayout hl = new HorizontalLayout();
final Button addCandidate = new Button("Add Candidate");
addCandidate.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, "new"));
});
hl.add(addCandidate);
final VGrid<Candidate> grid = new VGrid<>(Candidate.class);
grid.setColumns("id", "email");
grid.setAllRowsVisible(true);
grid.addComponentColumn((ValueProvider<Candidate, Component>) candidate -> {
final Button edit = new Button("Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, candidate.getId().toString())));
return edit;
});
grid.setDataProvider(new DataProvider<>() {
@Override
public boolean isInMemory() {
return false;
}
@Override
public int size(final Query<Candidate, Object> query) {
return candidateService.getCandidates().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Candidate> fetch(final Query<Candidate, Object> query) {
int limit = query.getLimit();
int pagerSize = query.getPageSize();
int page = query.getPage();
return candidateService.getCandidates().stream();
}
@Override
public void refreshItem(final Candidate candidate) {
// no-op
}
@Override
public void refreshAll() {
// no-op
}
@Override
public Registration addDataProviderListener(final DataProviderListener<Candidate> dataProviderListener) {
return null;
}
});
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,16 +1,13 @@
package com.primefactorsolutions.views.employee;
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Document;
import com.primefactorsolutions.model.DocumentType;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.service.DocumentService;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.textfield.TextField;
@ -26,6 +23,7 @@ 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;
@ -40,29 +38,28 @@ import java.io.InputStream;
@Scope("prototype")
@PageTitle("Document")
@Route(value = "/documents/:documentId?/:action?", layout = MainLayout.class)
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");
private final Span pdfOnlyMessage = new Span("Únicamente se permite la carga de archivos en formato PDF.");
public class DocumentView extends BeanValidationForm<Document> implements HasUrlParameter<String> {
private final TextField fileName = new TextField("Document Name");
private final ComboBox<DocumentType> documentType = new ComboBox<>("Document Type");
private final ComboBox<Employee> employeeComboBox = new ComboBox<>("Employee");
private final MemoryBuffer buffer = new MemoryBuffer();
private final Upload uploadButton = new Upload(buffer);
private final DocumentService documentService;
private final EmployeeService employeeService;
private final AuthenticationContext authContext;
private boolean fileUploaded = false;
private Button saveButton;
private Button viewDocumentButton;
public DocumentView(final AuthenticationContext authenticationContext,
final DocumentService documentService,
public DocumentView(final DocumentService documentService,
final EmployeeService employeeService,
final AuthenticationContext authContext) {
super(authenticationContext, Document.class);
super(Document.class);
this.documentService = documentService;
this.employeeService = employeeService;
this.authContext = authContext;
initializeView();
this.setSavedHandler(this::saveDocument);
}
private void initializeView() {
@ -70,8 +67,20 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
configureUploadButton();
}
protected Button createSaveButton() {
saveButton = new Button("Save");
saveButton.addClickListener(event -> saveDocument());
return saveButton;
}
protected Button createCloseButton() {
Button closeButton = new Button("Close");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
protected Button createViewDocumentButton() {
viewDocumentButton = new Button("Ver documento");
viewDocumentButton = new Button("View Document");
viewDocumentButton.setEnabled(false);
viewDocumentButton.addClickListener(event -> viewDocument());
return viewDocumentButton;
@ -121,12 +130,17 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
ui.getPage().open(registration.getResourceUri().toString());
});
} catch (IOException e) {
Notification.show("Error al leer el archivo.");
Notification.show("Error reading file.");
}
}
private void saveDocument(final Document document) {
private void navigateToDocumentsListView() {
getUI().ifPresent(ui -> ui.navigate(DocumentsListView.class));
}
private void saveDocument() {
if (isFormValid()) {
Document document = getEntity();
document.setFileName(fileName.getValue());
document.setDocumentType(documentType.getValue());
document.setEmployee(employeeComboBox.getValue());
@ -134,12 +148,17 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
setDocumentCreator(document);
documentService.saveDocument(document);
getUI().ifPresent(ui -> ui.navigate(DocumentsListView.class));
Notification.show("File saved successfully.");
clearForm();
} else {
Notification.show("Error al guardar: Por favor, complete todos los campos y cargue un archivo.");
Notification.show("Save failed: Please complete all fields and upload a file.");
}
}
private void closeForm() {
navigateToDocumentsListView();
}
private boolean isFormValid() {
return !fileName.isEmpty()
&& documentType.getValue() != null
@ -147,11 +166,20 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
&& 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();
} catch (IOException e) {
Notification.show("Error al leer los datos del archivo.");
Notification.show("Error reading file data.");
return new byte[0];
}
}
@ -168,10 +196,23 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
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() {
@ -179,16 +220,31 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
uploadButton.setAcceptedFileTypes(".pdf");
uploadButton.addSucceededListener(event -> {
fileUploaded = true;
Notification.show("Archivo cargado correctamente.");
Notification.show("File uploaded successfully.");
viewDocumentButton.setEnabled(true);
updateSaveButtonState();
});
uploadButton.getElement().addEventListener("file-remove", event -> {
fileUploaded = false;
Notification.show("Archivo eliminado.");
Notification.show("File removed.");
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();
@ -200,30 +256,18 @@ public class DocumentView extends BaseEntityForm<Document> implements HasUrlPara
assert documentIdString != null;
UUID documentId = UUID.fromString(documentIdString);
Document document = documentService.getDocument(documentId);
setEntity(document);
employeeComboBox.setValue(document.getEmployee());
preLoadFile(document);
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);
}
configureViewOrEditAction(action, documentIdString);
}
}
@Override
protected List<Component> getFormComponents() {
final HorizontalLayout buttonLayout = new HorizontalLayout();
HorizontalLayout buttonLayout = new HorizontalLayout();
buttonLayout.add(uploadButton, createViewDocumentButton());
buttonLayout.setSpacing(true);
return List.of(documentType, fileName, employeeComboBox, pdfOnlyMessage, buttonLayout);
return List.of(fileName, documentType, employeeComboBox, buttonLayout, createCloseButton());
}
}

View File

@ -1,20 +1,16 @@
package com.primefactorsolutions.views.employee;
package com.primefactorsolutions.views;
import com.google.common.collect.Lists;
import com.primefactorsolutions.model.Document;
import com.primefactorsolutions.model.DocumentType;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.service.DocumentService;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
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.button.ButtonVariant;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.grid.GridSortOrder;
@ -30,7 +26,6 @@ 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.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
@ -53,10 +48,7 @@ public class DocumentsListView extends BaseView {
private ComboBox<Employee> employeeFilter;
private ComboBox<DocumentType> documentTypeFilter;
public DocumentsListView(final AuthenticationContext authenticationContext,
final DocumentService documentService,
final EmployeeService employeeService) {
super(authenticationContext);
public DocumentsListView(final DocumentService documentService, final EmployeeService employeeService) {
this.documentService = documentService;
this.employeeService = employeeService;
initializeView();
@ -64,7 +56,7 @@ public class DocumentsListView extends BaseView {
}
private void initializeView() {
getCurrentPageLayout().add(createActionButton("Añadir documento", this::navigateToAddDocumentView, true));
getCurrentPageLayout().add(createActionButton("Add Document", this::navigateToAddDocumentView));
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createDocumentTypeFilter());
@ -78,10 +70,7 @@ public class DocumentsListView extends BaseView {
private void configureDocumentGrid() {
documentGrid.setColumns("fileName", "documentType", "creator");
documentGrid.getColumnByKey("fileName").setHeader("Nombre archivo");
documentGrid.getColumnByKey("documentType").setHeader("Tipo");
documentGrid.getColumnByKey("creator").setHeader("Creador");
documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Empleado");
documentGrid.addComponentColumn(this::createEmployeeSpan).setHeader("Employee");
addActionColumns();
configurePagination();
}
@ -96,35 +85,29 @@ public class DocumentsListView extends BaseView {
documentGrid.addComponentColumn((ValueProvider<Document, Component>) document -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.EYE, "Ver");
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.EYE, "View");
viewItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
navigateToDocumentView(document));
final MenuItem editItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.PENCIL, "Editar");
final MenuItem editItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.PENCIL, "Edit");
editItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
navigateToEditDocumentView(document));
final MenuItem downloadItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.DOWNLOAD, "Descargar");
final MenuItem downloadItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.DOWNLOAD, "Download");
downloadItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
downloadDocument(document));
return menuBar;
});
}
private Button createActionButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
private Button createActionButton(final String label, final Runnable onClickAction) {
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 = new ComboBox<>("Document Type");
documentTypeFilter.setItems(DocumentType.values());
documentTypeFilter.setValue(DocumentType.values()[0]);
documentTypeFilter.addValueChangeListener(event -> {
updateDocumentGrid(event.getValue(), employeeFilter.getValue());
});
@ -132,32 +115,26 @@ public class DocumentsListView extends BaseView {
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
employeeFilter.setPlaceholder("Seleccionar ...");
employeeFilter.setClearButtonVisible(true);
final List<Employee> employees;
if (isRoleAdmin()) {
employees = employeeService.findAllEmployees();
employeeFilter.setItems(employees);
} else {
Employee employee = employeeService.getEmployee(getEmployeeId().get());
employees = Lists.newArrayList(employee);
employeeFilter.setItems(employees);
employeeFilter.setValue(employees.getFirst());
employeeFilter.setReadOnly(true);
}
employeeFilter = new ComboBox<>("Employee");
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("All");
return allEmployeesOption;
}
private String getEmployeeLabel(final Employee employee) {
return employee.getFirstName() + " " + employee.getLastName();
return employee.getFirstName().equals("All") ? "All" : employee.getFirstName() + " " + employee.getLastName();
}
private void navigateToEditDocumentView(final Document document) {

View File

@ -1,4 +1,4 @@
package com.primefactorsolutions.views.employee;
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.Team;
@ -6,8 +6,7 @@ import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.vaadin.componentfactory.pdfviewer.PdfViewer;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
@ -16,14 +15,13 @@ 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.*;
import com.vaadin.flow.component.icon.VaadinIcon;
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.notification.Notification;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.tabs.TabSheet;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.upload.Upload;
@ -32,17 +30,14 @@ import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.*;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.datepicker.VDatePicker;
import org.vaadin.firitin.fields.ElementCollectionField;
import org.vaadin.firitin.form.BeanValidationForm;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
@ -54,44 +49,56 @@ import java.util.UUID;
@Scope("prototype")
@PageTitle("Employee")
@Route(value = "/employees/:employeeId?/:action?", layout = MainLayout.class)
@Slf4j
public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlParameter<String> {
public class EmployeeView extends BeanValidationForm<Employee> implements HasUrlParameter<String> {
private final EmployeeService employeeService;
private final ReportService reportService;
private final TimeOffRequestService requestService;
private final TeamService teamService;
private final TabSheet tabSheet = new TabSheet();
// 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);
private final TextField username = createTextField("Username: ", 30, true);
private final TextField firstName = createTextField("Nombres: ", 30, true);
private final TextField lastName = createTextField("Apellidos", 30, true);
private final ComboBox<Employee.Status> status = createStatusComboBox();
private final ComboBox<Employee.Gender> gender = createGenderComboBox();
private final VDatePicker birthday = new VDatePicker("Fecha de Nacimiento");
private final TextField age = createTextField("Edad", 3, false);
private final TextField birthCity = createTextField("Ciudad y País de Nacimiento", 30, false);
private final TextField birthCity = createTextField("Ciudad y País de Nacimiento ejemplo: (Ciudad, País) ",
30, false);
private final TextField residenceAddress = createTextField("Dirección de Residencia", 50, false);
private final TextField localAddress = createTextField("Departamento y Provincia de Residencia", 30, false);
private final TextField localAddress = createTextField("Departamento y Provincia de Residencia "
+ " ejemplo: (Departamento-Provincia)", 30, false);
private final ComboBox<Employee.MaritalStatus> maritalStatus = createMaritalStatusComboBox();
private final TextField numberOfChildren = createTextField("Numero de Hijos", 1, false);
private final TextField ci = createTextField("CI", 10, false);
private final TextField issuedIn = createTextField("Lugar de Expedicion", 10, false);
private final TextField issuedIn = createTextField("Expedido en ", 10, false);
private final TextField phoneNumber = createTextField("Teléfono", 8, false);
private final EmailField personalEmail = createEmailField("E-mail");
private final TextField phoneNumberProfessional = createTextField("Teléfono Laboral", 8, false);
private final EmailField personalEmail = createEmailField("E-mail ejemplo: (ejemplo@gmail.com)");
private final TextField phoneNumberProfesional = createTextField("Teléfono Laboral", 8, false);
private final EmailField profesionalEmail = createEmailField("E-mail Laboral ejemplo: "
+ "(ejemplo@primerfactorsolutions.com)");
private final TextField emergencyCName = createTextField("Nombres y Apellidos de Contacto", 50, false);
private final TextField emergencyCAddress = createTextField("Dirección de Contacto", 50, false);
private final TextField emergencyCPhone = createTextField("Teléfono de Contacto", 8, false);
private final EmailField emergencyCEmail = createEmailField("Email de Contacto");
private final EmailField emergencyCEmail = createEmailField("Email de Contacto ejemplo: (ejemplo@gmail.com)");
private final MemoryBuffer buffer = new MemoryBuffer();
private final Upload upload = new Upload(buffer);
private final Image profileImagePreview = new Image();
private final ElementCollectionField<Certification> educationTitles =
new ElementCollectionField<>(Certification.class);
private final ElementCollectionField<Certification> certifications =
new ElementCollectionField<>(Certification.class);
private final TextField pTitle1 = createTextField("Título 1", 30, false);
private final TextField pTitle2 = createTextField("Título 2", 30, false);
private final TextField pTitle3 = createTextField("Título 3", 30, false);
private final TextField pStudy1 = createTextField("Estudio 1", 30, false);
private final TextField pStudy2 = createTextField("Estudio 2", 30, false);
private final TextField pStudy3 = createTextField("Estudio 3", 30, false);
private final TextField certification1 = createTextField("Certificación 1", 30, false);
private final TextField certification2 = createTextField("Certificación 2", 30, false);
private final TextField certification3 = createTextField("Certificación 3", 30, false);
private final TextField certification4 = createTextField("Certificación 4", 30, false);
private final TextField recognition = createTextField("Reconocimientos", 30, false);
private final TextField achievements = createTextField("Logros Profesionales", 30, false);
private final ElementCollectionField<Language> languages = new ElementCollectionField<>(Language.class);
private final TextField language1 = createTextField("Idioma 1", 30, false);
private final TextField language1Level = createTextField("Nivel de Idioma", 30, false);
private final TextField language2 = createTextField("Idioma 2", 30, false);
private final TextField language2Level = createTextField("Nivel de Idioma", 30, false);
private final TextField cod = createTextField("Codigo de Empleado", 20, false);
private final TextField position = createTextField("Cargo", 30, false);
private final ComboBox<Team> team = new ComboBox<>("Equipo");
@ -99,58 +106,52 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
private final VDatePicker dateOfEntry = new VDatePicker("Fecha de Ingreso");
private final VDatePicker dateOfExit = new VDatePicker("Fecha de Retiro");
private final ComboBox<Employee.ContractType> contractType = createContractTypeComboBox();
private final TextField customContractType = createCustomContractTypeField();
private final TextField seniority = createTextField("Antiguedad", 30, false);
private final BigDecimalField salaryTotal = createBigDecimalField("Salario Total", false);
private final BigDecimalField salaryBasic = createBigDecimalField("Salario Basico", false);
private final BigDecimalField tenureBonus = createBigDecimalField("Bono de Antiguedad", false);
private final BigDecimalField professionalBonus = createBigDecimalField("Bono Profesional", false);
private final TextField bankName = createTextField("Banco", 30, false);
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 bonoProfesional = createTextField("Bono Profesional", 30, false);
private final TextField bankName = createTextField("Banco", 30, false);
private final TextField accountNumber = createTextField("Nro. de Cuenta", 30, false);
private final TextField gpss = createTextField("Código Único de Asegurado (GPSS)", 30, false);
private final TextField sss = createTextField("Matricula de Asegurado (SSS)", 30, false);
private final TextField beneficiarie1 = createTextField("Derechohabiente 1", 30, false);
private final TextField beneficiarie2 = createTextField("Derechohabiente 2", 30, false);
private static final String SAVE_BUTTON_TEXT = "Save";
private static final String EDIT_BUTTON_TEXT = "Edit";
private static final String NOTIFICATION_SAVE_SUCCESS = "Employee saved successfully.";
private static final String NOTIFICATION_VALIDATE_ERROR = "Please complete the required fields correctly.";
private static final String PHONE_NUMBER_ERROR_MESSAGE = "El teléfono debe contener solo números.";
private final Button reportButton = new Button("Generar Ficha de Contratación");
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 Dialog dialog = new Dialog();
private final PdfViewer pdfViewer = new PdfViewer();
private final Button excelReportButton = new Button("Información General del Empleado Excel",
VaadinIcon.FILE_TABLE.create());
private final H3 generalInfo = new H3("Información General");
private final H5 imagenSub = new H5("Insertar una imagen .jpg:");
private final H2 infoPer = new H2("Información Personal");
private final H3 infoGenr = new H3("Información General");
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");
private final H3 certif = new H3("Certificaciones Profesionales");
private final H3 logros = new H3("Otros Logros y Reconocimientos");
private final H3 idioma = new H3("Dominio de Idiomas");
private final H2 titleAdminInfo = new H2("Información Administrativa");
private final H2 infoAdm = new H2("Información Administrativa");
private final H3 infoCont = new H3("Información de Contratación");
private final H3 datBanc = new H3("Datos Bancarios");
private final H3 datGest = new H3("Datos Gestora Pública y Seguro Social");
public EmployeeView(final AuthenticationContext authenticationContext,
final EmployeeService employeeService,
public EmployeeView(final EmployeeService employeeService,
final ReportService reportService,
final TeamService teamService) {
super(authenticationContext, Employee.class);
final TeamService teamService,
final TimeOffRequestService requestService) {
super(Employee.class);
this.employeeService = employeeService;
this.reportService = reportService;
this.requestService = requestService;
this.teamService = teamService;
excelReportButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
saveButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
configureComponents();
addClassName("main-layout");
excelReportButton.addClickListener(e ->
getUI().ifPresent(ui ->
ui.navigate(EmployeeReportView.class, getEntity().getId().toString())
)
);
setSavedHandler(this::saveEmployee);
}
private void makeUpperCase(final TextField textField) {
@ -163,7 +164,6 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
}
private void configureComponents() {
tabSheet.setWidthFull();
phoneNumber.setValueChangeMode(ValueChangeMode.EAGER);
phoneNumber.addValueChangeListener(e -> validatePhoneNumber(phoneNumber, e.getValue()));
emergencyCPhone.setValueChangeMode(ValueChangeMode.EAGER);
@ -174,19 +174,11 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
lastName.addValueChangeListener(e -> validateNameField(lastName, e.getValue()));
createTeamComboBox();
configureUpload();
saveButton.setVisible(true);
editButton.setVisible(true);
reportButton.setVisible(true);
birthday.addValueChangeListener(event -> calculateAge());
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);
}
});
birthday.setMax(java.time.LocalDate.now().minusYears(18));
salaryTotal.addValueChangeListener(event -> calculateSalaryTotal());
dateOfEntry.addValueChangeListener(event -> calculateSeniority());
dateOfExit.addValueChangeListener(event -> {
@ -214,8 +206,22 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
makeUpperCase(emergencyCAddress);
makeUpperCase(ci);
makeUpperCase(issuedIn);
makeUpperCase(pTitle1);
makeUpperCase(pTitle2);
makeUpperCase(pTitle3);
makeUpperCase(pStudy1);
makeUpperCase(pStudy2);
makeUpperCase(pStudy3);
makeUpperCase(certification1);
makeUpperCase(certification2);
makeUpperCase(certification3);
makeUpperCase(certification4);
makeUpperCase(recognition);
makeUpperCase(achievements);
makeUpperCase(language1);
makeUpperCase(language1Level);
makeUpperCase(language2);
makeUpperCase(language2Level);
makeUpperCase(cod);
makeUpperCase(leadManager);
makeUpperCase(seniority);
@ -244,6 +250,7 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
int ages = currentYear - birthYear;
age.setValue(String.valueOf(ages));
birthday.setInvalid(ages < 18);
System.out.println(age);
}
}
@ -262,16 +269,16 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
private void calculateSalaryTotal() {
if (contractType.getValue() == Employee.ContractType.CONTRATO_LABORAL) {
salaryBasic.setVisible(true);
professionalBonus.setVisible(true);
tenureBonus.setVisible(true);
bonoProfesional.setVisible(true);
antiguedad.setVisible(true);
salaryTotal.setVisible(true);
salaryBasic.addValueChangeListener(event -> updateTotalSalary());
professionalBonus.addValueChangeListener(event -> updateTotalSalary());
tenureBonus.addValueChangeListener(event -> updateTotalSalary());
bonoProfesional.addValueChangeListener(event -> updateTotalSalary());
antiguedad.addValueChangeListener(event -> updateTotalSalary());
} else {
salaryBasic.setVisible(false);
professionalBonus.setVisible(false);
tenureBonus.setVisible(false);
bonoProfesional.setVisible(false);
antiguedad.setVisible(false);
salaryTotal.setVisible(true);
}
salaryTotal.getValue();
@ -279,13 +286,13 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
private void updateTotalSalary() {
try {
final BigDecimal basic = salaryBasic.getValue();
final BigDecimal bonus = professionalBonus.getValue();
final BigDecimal seniorityBonus = tenureBonus.getValue();
final BigDecimal totalSalary = basic.add(bonus).add(seniorityBonus);
salaryTotal.setValue(totalSalary);
double basic = parseDoubleValue(salaryBasic.getValue());
double bonus = parseDoubleValue(bonoProfesional.getValue());
double seniorityBonus = parseDoubleValue(antiguedad.getValue());
double totalSalary = basic + bonus + seniorityBonus;
salaryTotal.setValue(String.valueOf(totalSalary));
} catch (Exception e) {
salaryTotal.setValue(BigDecimal.valueOf(0.0));
salaryTotal.setValue("0.0");
}
}
@ -311,10 +318,10 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
profileImagePreview.setMaxHeight("150px");
} catch (IOException e) {
Notification.show("Error al subir la imagen: " + e.getMessage());
log.error("Error uploading image", e);
e.printStackTrace();
} catch (Exception e) {
Notification.show("Error en el servidor al procesar la imagen.");
log.error("Error uploading image", e);
e.printStackTrace();
}
});
}
@ -366,29 +373,10 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
comboBox.setItemLabelGenerator(Employee.ContractType::name);
comboBox.setRequiredIndicatorVisible(true);
comboBox.setWidth("300px");
comboBox.addValueChangeListener(event -> handleContractTypeChange(event.getValue()));
comboBox.setMinWidth("200px");
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%");
@ -403,13 +391,6 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
return textField;
}
private BigDecimalField createBigDecimalField(final String label, final boolean required) {
BigDecimalField textField = new BigDecimalField(label);
textField.setWidthFull();
textField.setRequired(required);
return textField;
}
private EmailField createEmailField(final String label) {
EmailField emailField = new EmailField(label);
emailField.setWidthFull();
@ -424,6 +405,14 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
team.setWidthFull();
}
private <T> ComboBox<T> createComboBox(final String label, final T[] items) {
ComboBox<T> comboBox = new ComboBox<>(label);
comboBox.setItems(items);
comboBox.setItemLabelGenerator(Object::toString);
comboBox.setWidthFull();
return comboBox;
}
private ComboBox<Employee.Gender> createGenderComboBox() {
ComboBox<Employee.Gender> comboBox = new ComboBox<>("Genero");
comboBox.setItems(Employee.Gender.values());
@ -456,20 +445,15 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
}
}
private void saveEmployee(final Employee employee) {
private void saveEmployee() {
if (validateForm()) {
Employee employee = getEntity();
employee.setStatus(status.getValue());
employee.setAge(age.getValue());
employee.setSalaryBasic(salaryBasic.getValue());
employee.setProfessionalBonus(professionalBonus.getValue());
employee.setTenureBonus(tenureBonus.getValue());
employee.setSalaryTotal((salaryTotal.getValue()));
employee.setContractType(contractType.getValue());
if (contractType.getValue() == Employee.ContractType.OTROS) {
employee.setCustomContractType(customContractType.getValue());
} else {
employee.setCustomContractType(null);
}
employee.setBonoProfesional(bonoProfesional.getValue());
employee.setAntiguedad(antiguedad.getValue());
employee.setSalarytotal((salaryTotal.getValue()));
employeeService.createOrUpdate(employee);
Notification.show(NOTIFICATION_SAVE_SUCCESS);
getUI().ifPresent(ui -> ui.navigate(EmployeesListView.class));
@ -480,44 +464,41 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
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();
final String s = params.get("employeeId").orElse(null);
if ("new".equals(action)) {
setEntityWithEnabledSave(new Employee());
saveButton.setVisible(true);
editButton.setVisible(false);
setFieldsEditable();
upload.setVisible(true);
profileImagePreview.setVisible(true);
salaryTotal.setValue(BigDecimal.valueOf(0.0));
salaryTotal.setValue(String.valueOf(true));
} else {
final Employee employee;
if (s != null) {
final UUID employeeId = UUID.fromString(s);
employee = employeeService.getEmployee(employeeId);
} else {
employee = employeeService.getEmployee(getEmployeeId().get());
}
if ("edit".equals(action)) {
setEntityWithEnabledSave(employee);
setEditHandler(null);
UUID employeeId = UUID.fromString(s);
var employee = employeeService.getEmployee(employeeId);
setEntityWithEnabledSave(employee);
if ("edit".equals(action) && !s.isEmpty()) {
saveButton.setVisible(true);
editButton.setVisible(false);
status.setValue(employee.getStatus());
setFieldsEditable();
upload.setVisible(true);
displayProfileImage(employee);
profileImagePreview.setVisible(true);
salaryTotal.setValue(employee.getSalaryTotal());
} else if ("view".equals(action) || "me".equals(action)) {
setEntity(employee);
setEditHandler(__ -> getUI().ifPresent(ui -> ui.navigate("/employees/" + employee.getId() + "/edit")));
salaryTotal.setValue(employee.getSalarytotal());
} else if ("view".equals(action) && !s.isEmpty()) {
setFieldsReadOnly();
saveButton.setVisible(false);
editButton.setVisible(true);
setFieldsReadOnly();
displayProfileImage(employee);
salaryTotal.setValue(employee.getSalaryTotal());
upload.setVisible(true);
salaryTotal.setValue(employee.getSalarytotal());
}
}
}
@ -548,35 +529,47 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
numberOfChildren.setReadOnly(true);
phoneNumber.setReadOnly(true);
personalEmail.setReadOnly(true);
phoneNumberProfessional.setReadOnly(true);
phoneNumberProfesional.setReadOnly(true);
profesionalEmail.setReadOnly(true);
position.setReadOnly(true);
team.setReadOnly(true);
emergencyCName.setReadOnly(true);
emergencyCAddress.setReadOnly(true);
emergencyCPhone.setReadOnly(true);
emergencyCEmail.setReadOnly(true);
upload.setVisible(true);
profileImagePreview.setVisible(true);
age.setReadOnly(true);
gender.setReadOnly(true);
status.setReadOnly(true);
ci.setReadOnly(true);
issuedIn.setReadOnly(true);
educationTitles.setReadOnly(true);
certifications.setReadOnly(true);
pTitle1.setReadOnly(true);
pTitle2.setReadOnly(true);
pTitle3.setReadOnly(true);
pStudy1.setReadOnly(true);
pStudy2.setReadOnly(true);
pStudy3.setReadOnly(true);
certification1.setReadOnly(true);
certification2.setReadOnly(true);
certification3.setReadOnly(true);
certification4.setReadOnly(true);
recognition.setReadOnly(true);
achievements.setReadOnly(true);
languages.setReadOnly(true);
language1.setReadOnly(true);
language1Level.setReadOnly(true);
language2.setReadOnly(true);
language2Level.setReadOnly(true);
cod.setReadOnly(true);
leadManager.setReadOnly(true);
dateOfEntry.setReadOnly(true);
dateOfExit.setReadOnly(true);
contractType.setReadOnly(true);
customContractType.setReadOnly(true);
seniority.setReadOnly(true);
salaryTotal.setReadOnly(true);
salaryBasic.setReadOnly(true);
professionalBonus.setReadOnly(true);
tenureBonus.setReadOnly(true);
bonoProfesional.setReadOnly(true);
antiguedad.setReadOnly(true);
bankName.setReadOnly(true);
accountNumber.setReadOnly(true);
gpss.setReadOnly(true);
@ -598,35 +591,47 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
numberOfChildren.setReadOnly(false);
phoneNumber.setReadOnly(false);
personalEmail.setReadOnly(false);
phoneNumberProfessional.setReadOnly(false);
phoneNumberProfesional.setReadOnly(false);
profesionalEmail.setReadOnly(false);
position.setReadOnly(false);
team.setReadOnly(false);
emergencyCName.setReadOnly(false);
emergencyCAddress.setReadOnly(false);
emergencyCPhone.setReadOnly(false);
emergencyCEmail.setReadOnly(false);
upload.setVisible(false);
profileImagePreview.setVisible(true);
age.setReadOnly(false);
gender.setReadOnly(false);
status.setReadOnly(false);
ci.setReadOnly(false);
issuedIn.setReadOnly(false);
educationTitles.setReadOnly(false);
certifications.setReadOnly(false);
pTitle1.setReadOnly(false);
pTitle2.setReadOnly(false);
pTitle3.setReadOnly(false);
pStudy1.setReadOnly(false);
pStudy2.setReadOnly(false);
pStudy3.setReadOnly(false);
certification1.setReadOnly(false);
certification2.setReadOnly(false);
certification3.setReadOnly(false);
certification4.setReadOnly(false);
recognition.setReadOnly(false);
achievements.setReadOnly(false);
languages.setReadOnly(false);
language1.setReadOnly(false);
language1Level.setReadOnly(false);
language2.setReadOnly(false);
language2Level.setReadOnly(false);
cod.setReadOnly(false);
leadManager.setReadOnly(false);
dateOfEntry.setReadOnly(false);
dateOfExit.setReadOnly(false);
contractType.setReadOnly(false);
customContractType.setReadOnly(false);
seniority.setReadOnly(false);
salaryTotal.setReadOnly(false);
salaryBasic.setReadOnly(false);
professionalBonus.setReadOnly(false);
tenureBonus.setReadOnly(false);
bonoProfesional.setReadOnly(false);
antiguedad.setReadOnly(false);
bankName.setReadOnly(false);
accountNumber.setReadOnly(false);
gpss.setReadOnly(false);
@ -637,78 +642,30 @@ public class EmployeeView extends BaseEntityForm<Employee> implements HasUrlPara
@Override
protected List<Component> getFormComponents() {
tabSheet.add("Info Personal", new VerticalLayout(
generalInfo,
imagenSub,
upload,
profileImagePreview,
firstName,
lastName,
gender,
status,
birthday,
age,
birthCity,
residenceAddress,
localAddress,
maritalStatus,
ci,
issuedIn,
numberOfChildren,
phoneNumber,
personalEmail,
phoneNumberProfessional,
contEmerg,
emergencyCName,
emergencyCAddress,
emergencyCPhone,
emergencyCEmail
));
tabSheet.add("Info Profesional", new VerticalLayout(
titulos,
educationTitles,
certif,
certifications,
logros,
recognition,
achievements,
idioma,
languages
));
if (isRoleAdmin()) {
tabSheet.add("Info Administrativa", new VerticalLayout(
cod,
position,
team,
leadManager,
infoCont,
dateOfEntry,
dateOfExit,
seniority,
contractType,
customContractType,
salaryBasic,
professionalBonus,
tenureBonus,
salaryTotal,
datBanc,
bankName,
accountNumber,
datGest,
gpss,
sss,
beneficiarie1,
beneficiarie2
));
}
return List.of(
new HorizontalLayout(reportButton, excelReportButton),
username,
tabSheet,
dialog
infoPer,
infoGenr,
upload, profileImagePreview,
firstName, lastName,
gender, status,
birthday, age,
birthCity, residenceAddress, localAddress,
maritalStatus, ci, issuedIn, numberOfChildren,
phoneNumber, personalEmail, phoneNumberProfesional, profesionalEmail,
contEmerg, emergencyCName, emergencyCAddress, emergencyCPhone, emergencyCEmail,
infProf,
titulos, pTitle1, pTitle2, pTitle3, pStudy1, pStudy2, pStudy3,
certif, certification1, certification2, certification3, certification4,
logros, recognition, achievements,
idioma, language1, language1Level, language2, language2Level,
infoAdm,
cod, position, team, leadManager,
infoCont, dateOfEntry, dateOfExit, contractType, seniority,
salaryBasic, bonoProfesional, antiguedad, salaryTotal,
datBanc, bankName, accountNumber,
datGest, gpss, sss, beneficiarie1, beneficiarie2,
saveButton, editButton, reportButton, dialog
);
}
}

View File

@ -0,0 +1,104 @@
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.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
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.util.List;
@SpringComponent
@Scope("prototype")
@PageTitle("Employees")
@Route(value = "/employees", layout = MainLayout.class)
@PermitAll
public class EmployeesListView extends BaseView {
private final EmployeeService employeeService;
private final PagingGrid<Employee> table = new PagingGrid<>(Employee.class);
public EmployeesListView(final EmployeeService employeeService) {
this.employeeService = employeeService;
setupView();
refreshGrid();
}
private void setupView() {
configureTable();
getCurrentPageLayout().add(createAddEmployeeButton());
getCurrentPageLayout().add(table);
}
private void configureTable() {
table.setColumns("firstName", "lastName", "status");
addEditButtonColumn("View", this::navigateToEmployeeView);
addEditButtonColumn("Edit", this::navigateToEditView);
setupPagingGrid();
}
private void addEditButtonColumn(final String label, final ButtonClickHandler handler) {
table.addComponentColumn(employee -> createButton(label, () -> handler.handle(employee)));
}
private Button createButton(final String label, final Runnable onClickAction) {
Button button = new Button(label);
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddEmployeeButton() {
return createButton("Add Employee", this::navigateToAddEmployeeView);
}
private void navigateToEditView(final Employee employee) {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, employee.getId().toString() + "/edit"));
}
private void navigateToEmployeeView(final Employee employee) {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, employee.getId().toString() + "/view"));
}
private void navigateToAddEmployeeView() {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, "new"));
}
private void setupPagingGrid() {
table.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
table.setPageSize(Constants.PAGE_SIZE);
}
private void refreshGrid() {
table.setPagingDataProvider((page, pageSize) -> fetchEmployees((int) page, pageSize));
}
private List<Employee> fetchEmployees(final int page, final int pageSize) {
int start = page * pageSize;
if (hasSortOrder()) {
return fetchSortedEmployees(start, pageSize);
}
return employeeService.findEmployees(start, pageSize);
}
private boolean hasSortOrder() {
return !table.getSortOrder().isEmpty();
}
private List<Employee> fetchSortedEmployees(final int start, final int pageSize) {
GridSortOrder<Employee> sortOrder = table.getSortOrder().getFirst();
return employeeService.findEmployees(start, pageSize,
sortOrder.getSorted().getKey(),
sortOrder.getDirection() == SortDirection.ASCENDING);
}
@FunctionalInterface
private interface ButtonClickHandler {
void handle(Employee employee);
}
}

View File

@ -5,7 +5,7 @@ import com.hilerio.ace.AceEditor;
import com.hilerio.ace.AceMode;
import com.hilerio.ace.AceTheme;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.AssessmentService;
import com.primefactorsolutions.service.CompilerService;
import com.vaadin.flow.component.*;
import com.vaadin.flow.component.button.Button;
@ -27,15 +27,19 @@ import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.theme.lumo.LumoUtility.Background;
import com.vaadin.flow.theme.lumo.LumoUtility.BoxSizing;
import com.vaadin.flow.theme.lumo.LumoUtility.Display;
import com.vaadin.flow.theme.lumo.LumoUtility.Flex;
import com.vaadin.flow.theme.lumo.LumoUtility.FlexDirection;
import com.vaadin.flow.theme.lumo.LumoUtility.FontSize;
import com.vaadin.flow.theme.lumo.LumoUtility.FontWeight;
import com.vaadin.flow.theme.lumo.LumoUtility.Gap;
import com.vaadin.flow.theme.lumo.LumoUtility.Height;
import com.vaadin.flow.theme.lumo.LumoUtility.Margin;
import com.vaadin.flow.theme.lumo.LumoUtility.Overflow;
import com.vaadin.flow.theme.lumo.LumoUtility.Padding;
import com.vaadin.flow.theme.lumo.LumoUtility.TextColor;
import io.overcoded.vaadin.panel.Panel;
import io.overcoded.vaadin.panel.PanelConfig;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Scope;
@ -45,23 +49,24 @@ import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
@PageTitle("Evaluacion")
@SpringComponent
@Scope("prototype")
@Route(value = "/candidate-exam", layout = MainLayout.class)
@Route(value = "/evaluation", layout = MainLayout.class)
@AnonymousAllowed
@Slf4j
public class CandidateExamView extends Main implements HasUrlParameter<String> {
public class EvaluationView extends Main implements HasUrlParameter<String> {
private final CompilerService compilerService;
private final ExamService examService;
private final AssessmentService assessmentService;
private AceEditor questionEditor = null;
private Dialog dialog = null;
private Dialog completeDialog = null;
private AceEditor result = null;
private Exam exam = null;
private Assessment assessment = null;
private Submission currSubmission = null;
private Boolean isCompleted = false;
@ -69,48 +74,33 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
private MenuItem prev = null;
private MenuItem next = null;
private MenuItem reset = null;
private MenuItem finish = null;
private Panel candidatePanel = null;
private Section sidebar = null;
private SimpleTimer timer = null;
private DescriptionList dl = null;
private Section editorSection = null;
private Section startSection = null;
private Section completedSection = null;
private H3 questionTitle = null;
private Text questionDescription = null;
public CandidateExamView(final CompilerService compilerService, final ExamService examService) {
public EvaluationView(final CompilerService compilerService, final AssessmentService assessmentService) {
this.compilerService = compilerService;
this.examService = examService;
this.assessmentService = assessmentService;
addClassNames(Display.FLEX, Flex.GROW, Height.FULL);
initStartSection();
initCompletedSection();
initEditorSection();
initResultDialog();
initCompleteDialog();
initTimer(examService);
initCandidatePanel();
initSidebar();
VerticalLayout vl = new VerticalLayout();
vl.add(completedSection, candidatePanel, startSection, editorSection, dialog);
add(vl);
add(completedSection, startSection, editorSection, sidebar, dialog);
updateUI();
}
private void initTimer(final ExamService examService) {
timer = new SimpleTimer(0);
timer.setMinutes(true);
timer.addTimerEndEvent((ComponentEventListener<SimpleTimer.TimerEndedEvent>) timerEndedEvent -> {
Notification.show("Tiempo completado.", 5_000, Notification.Position.TOP_CENTER);
this.currSubmission.setText(this.questionEditor.getValue());
this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.exam = examService.completeExam(exam.getId());
goToCompleted();
updateUI();
});
timer.setFractions(false);
}
private void initResultDialog() {
dialog = new Dialog();
dialog.setHeaderTitle("Resultados");
@ -138,8 +128,8 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
dialog.add(dialogLayout);
final Button saveButton = new Button("Guardar y Siguiente", e -> {
this.currSubmission.setText(this.questionEditor.getValue());
this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
dialog.close();
goToNext();
});
@ -166,9 +156,9 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
final Button completeButton = new Button("Terminar", e -> {
completeDialog.close();
this.currSubmission.setText(this.questionEditor.getValue());
this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.exam = examService.completeExam(exam.getId());
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
this.assessment = assessmentService.completeAssessment(assessment.getId());
goToCompleted();
updateUI();
});
@ -205,26 +195,23 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
final MenuBar navMenuBar = new MenuBar();
prev = navMenuBar.addItem("Anterior pregunta",
menuItemClickEvent -> {
this.currSubmission.setText(this.questionEditor.getValue());
this.examService.saveSubmission(exam.getId(), this.currSubmission);
this.currSubmission = this.examService.getPrevSubmission(exam.getId(), this.currSubmission);
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(), this.currSubmission);
updateUI();
});
next = navMenuBar.addItem("Siguiente pregunta",
menuItemClickEvent -> {
this.currSubmission.setText(this.questionEditor.getValue());
this.examService.saveSubmission(exam.getId(), this.currSubmission);
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
goToNext();
});
reset = navMenuBar.addItem("Reiniciar pregunta (deshacer todos los cambios)",
menuItemClickEvent -> {
this.currSubmission.setText(this.currSubmission.getQuestion().getContent());
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setResponse(this.currSubmission.getQuestion().getContent());
this.questionEditor.setValue(this.currSubmission.getQuestion().getContent());
});
finish = navMenuBar.addItem("Terminar evaluacion", menuItemClickEvent -> {
this.completeDialog.open();
});
final Div menuBar = new Div();
menuBar.add(runMenuBar, navMenuBar);
@ -265,9 +252,9 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
start = new Button("Empezar");
start.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_SUCCESS);
start.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.exam = this.examService.startExam(this.exam.getId());
this.assessment = this.assessmentService.startAssessment(this.assessment.getId());
if (tf.getValue().trim().equalsIgnoreCase(this.exam.getCandidate().getEmail())) {
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) {
this.getUI().get().getPage().reload();
} else {
Notification notification = new Notification();
@ -329,7 +316,7 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
}
private void goToNext() {
Submission found = this.examService.getNextSubmission(exam.getId(),
Submission found = this.assessmentService.getNextSubmission(assessment.getId(),
this.currSubmission.getId());
if (found == null) {
@ -340,44 +327,78 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
}
}
private void initCandidatePanel() {
final PanelConfig config = PanelConfig.builder()
.closeable(false)
.collapsable(false)
.build();
private void initSidebar() {
sidebar = new Section();
sidebar.addClassNames(Background.CONTRAST_5, BoxSizing.BORDER, Display.FLEX, FlexDirection.COLUMN,
Flex.SHRINK_NONE, Overflow.AUTO, Padding.LARGE);
sidebar.setWidth("256px");
candidatePanel = new Panel(config, "", new HorizontalLayout());
candidatePanel.setVisible(false);
dl = new DescriptionList();
dl.addClassNames(Display.FLEX, FlexDirection.COLUMN, Gap.LARGE, Margin.Bottom.SMALL, Margin.Top.NONE,
FontSize.SMALL);
final Text text = new Text("Tiempo restante:");
timer = new SimpleTimer(0);
timer.setMinutes(true);
timer.addTimerEndEvent((ComponentEventListener<SimpleTimer.TimerEndedEvent>) timerEndedEvent -> {
Notification.show("Tiempo completado.", 5_000, Notification.Position.TOP_CENTER);
this.currSubmission.setResponse(this.questionEditor.getValue());
this.assessmentService.saveSubmission(assessment.getId(), this.currSubmission);
this.assessment = assessmentService.completeAssessment(assessment.getId());
goToCompleted();
updateUI();
});
timer.setFractions(false);
final Button completeButton = new Button("Terminar evaluacion");
completeButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY, ButtonVariant.LUMO_ERROR);
completeButton.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.completeDialog.open();
});
// Add it all together
sidebar.add(dl, text, timer, completeButton);
}
private void updateUI() {
if (exam == null || !exam.isStarted()) {
if (assessment == null || !assessment.isStarted()) {
editorSection.setVisible(false);
startSection.setVisible(true);
sidebar.setVisible(false);
} else {
if (currSubmission != null) {
questionEditor.setValue(this.currSubmission.getText());
questionEditor.setValue(this.currSubmission.getResponse());
questionTitle.setText(this.currSubmission.getQuestion().getTitle());
questionDescription.setText(this.currSubmission.getQuestion().getDescription());
}
editorSection.setVisible(true);
startSection.setVisible(false);
sidebar.setVisible(true);
updateCandidatePanel();
prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission));
prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !exam.isLast(currSubmission));
if (this.exam.isCompleted()) {
if (this.assessment.isCompleted()) {
goToCompleted();
this.editorSection.setVisible(false);
this.candidatePanel.setVisible(false);
this.sidebar.setVisible(false);
this.startSection.setVisible(false);
this.completedSection.setVisible(true);
}
final Long remainingTime = this.exam.getRemainingTimeSeconds();
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add(
createItem("Candidato:", assessment.getCandidate().getEmail()),
createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t,
ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
.orElse("N/A"))
);
}
final Long remainingTime = this.assessment.getRemainingTimeSeconds();
timer.pause();
timer.setStartTime(remainingTime > 0 ? remainingTime : 3);
timer.setMinutes(true);
@ -386,19 +407,6 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
}
}
private void updateCandidatePanel() {
final Text candidateName = new Text("Candidato: " + exam.getCandidate().getEmail() + ", ");
final Text startTime = new Text("Hora de inicio: " + Optional.ofNullable(exam.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t,
ZoneId.of("GMT-4")).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")))
.orElse("N/A") + ", ");
final Text text = new Text("Tiempo restante:");
final HorizontalLayout layout = new HorizontalLayout(candidateName, startTime, text, timer);
layout.setWidthFull();
candidatePanel.setContent(layout);
candidatePanel.setVisible(true);
}
private Div createItem(final String label, final String value) {
return new Div(createTerm(label), createDescription(value));
}
@ -426,18 +434,18 @@ public class CandidateExamView extends Main implements HasUrlParameter<String> {
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
this.exam = this.examService.getExam(UUID.fromString(s));
this.assessment = this.assessmentService.getAssessment(UUID.fromString(s));
if (this.exam == null) {
if (this.assessment == null) {
throw new NotFoundException();
}
if (this.exam.isCompleted()) {
if (this.assessment.isCompleted()) {
goToCompleted();
}
this.currSubmission = this.exam.isStarted()
? this.examService.getNextSubmission(exam.getId(), null)
this.currSubmission = this.assessment.isStarted()
? this.assessmentService.getNextSubmission(assessment.getId(), null)
: null;
updateUI();

View File

@ -0,0 +1,279 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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.notification.Notification;
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.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.time.LocalDate;
import java.time.temporal.IsoFields;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Registro de Horas Trabajadas")
@Route(value = "/hours-worked-list", layout = MainLayout.class)
public class HoursWorkedListView extends BaseView {
private final HoursWorkedService hoursWorkedService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<HoursWorked> hoursWorkedGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
public HoursWorkedListView(final HoursWorkedService hoursWorkedService,
final EmployeeService employeeService,
final TeamService teamService) {
this.hoursWorkedService = hoursWorkedService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
refreshGridListHoursWorked(null, null);
}
private void refreshGridListHoursWorked(final Employee employee,
final Team team) {
hoursWorkedGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * hoursWorkedGrid.getPageSize());
List<HoursWorked> hoursWorkedList = fetchFilteredHoursWorked(start, pageSize, employee, team);
double totalHours = hoursWorkedList.stream()
.mapToDouble(HoursWorked::getTotalHours)
.sum();
Notification.show("Total de horas trabajadas: " + totalHours,
3000, Notification.Position.BOTTOM_CENTER);
return hoursWorkedList;
});
hoursWorkedGrid.getDataProvider().refreshAll();
}
private List<HoursWorked> fetchFilteredHoursWorked(final int start,
final int pageSize,
final Employee employee,
final Team team) {
List<HoursWorked> filteredHoursWorked = hoursWorkedService.findAll();
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredHoursWorked = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"TODOS".equals(team.getName())) {
filteredHoursWorked = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getTeam() != null
&& hw.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
for (HoursWorked hoursWorked : filteredHoursWorked) {
if (employee != null && hoursWorked.getEmployee().getId().equals(employee.getId())) {
LocalDate date = hoursWorked.getDate();
int currentWeek = date.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
double totalWorkedInSameWeek = filteredHoursWorked.stream()
.filter(hw -> hw.getEmployee().getId().equals(employee.getId())
&&
hw.getDate().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR) == currentWeek)
.mapToDouble(HoursWorked::getHours)
.sum();
double updatedPendingHours = totalWorkedInSameWeek - hoursWorked.getHours();
hoursWorked.setHoraspendientes(updatedPendingHours);
}
}
int end = Math.min(start + pageSize, filteredHoursWorked.size());
return filteredHoursWorked.subList(start, end);
}
private void initializeView() {
getCurrentPageLayout().add(createAddHoursWorked());
setupFilters();
setupListHoursWorkedGrid();
getCurrentPageLayout().add(hoursWorkedGrid);
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
getCurrentPageLayout().add(hl);
}
private void setupListHoursWorkedGrid() {
hoursWorkedGrid.addColumn(hw -> hw.getDate() != null ? hw.getDate().toString() : "")
.setHeader("Fecha")
.setSortable(true);
hoursWorkedGrid.addColumn(HoursWorked::getWeekNumber)
.setHeader("Semana")
.setSortable(true);
hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getFirstName() + " " + hw.getEmployee().getLastName())
.setHeader("Empleado");
hoursWorkedGrid.addColumn(hw -> hw.getEmployee().getTeam() != null ? hw.getEmployee().getTeam()
.getName() : "Sin asignar")
.setHeader("Equipo");
hoursWorkedGrid.addColumn(hw -> {
String actividad = hw.getActividad() != null ? hw.getActividad() : "Sin Actividad";
String tareaEspecifica = hw.getTareaEspecifica() != null ? hw.getTareaEspecifica() : "";
return !tareaEspecifica.isEmpty() ? tareaEspecifica : actividad;
}).setHeader("Actividad");
hoursWorkedGrid.addColumn(hw -> {
if (hw.getTareaEspecifica() != null && !hw.getTareaEspecifica().isEmpty()) {
return calcularHorasPorTareaEspecifica(hw);
} else {
return calcularHorasPorActividadGeneral(hw);
}
}).setHeader("Total Horas").setSortable(true);
hoursWorkedGrid.addColumn(hw -> hw.getHoraspendientes() - calcularTotal(hw)).setHeader("Horas Pendientes")
.setSortable(true);
hoursWorkedGrid.addComponentColumn((ValueProvider<HoursWorked, Component>) hoursWorked -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.EYE, "Ver");
viewItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
navigateToHoursWorkedView(hoursWorked.getEmployee().getId());
});
return menuBar;
});
hoursWorkedGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
hoursWorkedGrid.setPageSize(PAGE_SIZE);
}
private double calcularHorasPorTareaEspecifica(final HoursWorked hoursWorked) {
List<HoursWorked> tareas = hoursWorkedService.findListHoursWorkedEmployee(
hoursWorked.getEmployee().getId(), hoursWorked.getWeekNumber());
return tareas.stream()
.filter(hw -> Objects.equals(hw.getTareaEspecifica(), hoursWorked.getTareaEspecifica()))
.mapToDouble(HoursWorked::getHours)
.sum();
}
private double calcularHorasPorActividadGeneral(final HoursWorked hoursWorked) {
List<HoursWorked> actividades = hoursWorkedService.findListHoursWorkedEmployee(
hoursWorked.getEmployee().getId(), hoursWorked.getWeekNumber());
return actividades.stream()
.filter(hw -> Objects.equals(hw.getActividad(), hoursWorked.getActividad())
&& (hw.getTareaEspecifica() == null || hw.getTareaEspecifica().isEmpty()))
.mapToDouble(HoursWorked::getHours)
.sum();
}
private void navigateToHoursWorkedView(final UUID idEmployee) {
getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + idEmployee.toString()));
}
private double calcularTotal(final HoursWorked hoursWorked) {
List<HoursWorked> listHoursworkedemploye = hoursWorkedService.findListHoursWorkedEmployee(
hoursWorked.getEmployee().getId(), hoursWorked.getWeekNumber());
return calculateTotalUtilized(listHoursworkedemploye);
}
private double calculateTotalUtilized(final List<HoursWorked> employeeRequests) {
return employeeRequests.stream()
.filter(Objects::nonNull)
.mapToDouble(HoursWorked::getHours)
.sum();
}
private Button createButton(final String label, final Runnable onClickAction) {
final Button button = new Button(label);
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddHoursWorked() {
return createButton("Agregar Actividad", this::navigateToHours);
}
private void navigateToHours() {
getUI().ifPresent(ui -> ui.navigate(HoursWorkedView.class, "new"));
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
event.getValue(),
teamFilter.getValue()
)
);
return employeeFilter;
}
private String getEmployeeFullName(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
employeeFilter.getValue(),
event.getValue()
)
);
return teamFilter;
}
private String getTeamLabel(final Team team) {
return team != null && !"TODOS".equals(team.getName()) ? team.getName() : "TODOS";
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
}

View File

@ -0,0 +1,295 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
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;
import java.time.temporal.IsoFields;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Horas Trabajadas")
@Route(value = "/hours-worked-list/:hours-workedId?/:action?", layout = MainLayout.class)
public class HoursWorkedView extends BeanValidationForm<HoursWorked> implements HasUrlParameter<String> {
private final VDatePicker dateField = new VDatePicker("Fecha");
private final ComboBox<Team> teamField = new ComboBox<>("Equipo");
private ComboBox<Employee> employeeField;
private final ComboBox<String> tareasEspecificasDropdown = new ComboBox<>("Tarea Específica");
private final TextField tareaEspecificaInput = new TextField("Otra Tarea Específica");
private final TextField horasTareaEspecificaField = new TextField("Horas Tarea Específica");
private final TextField activityField = new TextField("Actividad");
private final TextField hoursField = new TextField("Horas");
private final H2 equipoLabel = new H2("Tareas del Cliente/Equipo");
private final H2 empresaLabel = new H2("Tareas de la Empresa");
private final Label totalCompletadoLabel = new Label();
private final HoursWorkedService hoursWorkedService;
private final EmployeeService employeeService;
private final TeamService teamService;
private HoursWorked hoursWorked;
private Employee employee;
private Button saveButton;
public HoursWorkedView(final HoursWorkedService hoursWorkedService,
final EmployeeService employeeService,
final TeamService teamService) {
super(HoursWorked.class);
this.hoursWorkedService = hoursWorkedService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeDateField();
initializeTeamField();
initializeEmployeeField();
configureTareasEspecificas();
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String s = params.get("hours-workedId").orElse(null);
if ("new".equals(action)) {
setEntityWithEnabledSave(new HoursWorked());
} else {
UUID hoursWorkedId = UUID.fromString(s);
var hoursWorked = hoursWorkedService.getHoursWorked(hoursWorkedId);
setEntityWithEnabledSave(hoursWorked);
if ("edit".equals(action) && !s.isEmpty()) {
saveButton.setVisible(true);
} else if ("view".equals(action) && !s.isEmpty()) {
saveButton.setVisible(false);
}
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(
dateField,
teamField,
employeeField,
equipoLabel,
activityField,
hoursField,
empresaLabel,
tareasEspecificasDropdown,
tareaEspecificaInput,
horasTareaEspecificaField,
createCloseButton()
);
}
private void configureTareasEspecificas() {
tareasEspecificasDropdown.setItems("Entrevistas", "Reuniones",
"Colaboraciones", "Aprendizajes", "Proyectos PFS",
"Consulta Medica", "Afiliación al Seguro", "Fallas Tecnicas", "Otros");
tareasEspecificasDropdown.setPlaceholder("Selecciona una tarea...");
tareasEspecificasDropdown.addValueChangeListener(event -> {
String selected = event.getValue();
boolean isOtros = "Otros".equals(selected);
tareaEspecificaInput.setVisible(isOtros);
horasTareaEspecificaField.setVisible(true);
if (!isOtros) {
tareaEspecificaInput.clear();
horasTareaEspecificaField.clear();
}
});
tareaEspecificaInput.setVisible(false);
horasTareaEspecificaField.setVisible(false);
}
protected Button createSaveButton() {
saveButton = new Button("Guardar");
saveButton.addClickListener(event -> saveHoursWorked());
return saveButton;
}
protected Button createCloseButton() {
Button closeButton = new Button("Cerrar");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
private void initializeTeamField() {
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamField.setItems(teamService.findAllTeams());
teamField.setItemLabelGenerator(Team::getName);
teamField.setValue(teams.getFirst());
teamField.addValueChangeListener(event -> {
if (teams != null) {
employeeField.getValue();
event.getValue();
}
}
);
}
private ComboBox<Employee> initializeEmployeeField() {
employeeField = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employeeField.setItems(employees);
employeeField.setItemLabelGenerator(this::getEmployeeFullName);
employeeField.setValue(employees.getFirst());
return employeeField;
}
private String getEmployeeFullName(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private void initializeDateField() {
LocalDate today = LocalDate.now();
YearMonth currentMonth = YearMonth.of(today.getYear(), today.getMonth());
LocalDate startOfMonth = currentMonth.atDay(1);
LocalDate maxSelectableDate = today;
dateField.setMin(startOfMonth);
dateField.setMax(maxSelectableDate);
dateField.setValue(today);
dateField.addValueChangeListener(event -> {
LocalDate selectedDate = event.getValue();
if (selectedDate != null) {
int weekNumber = selectedDate.get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
Notification.show("Número de la semana: " + weekNumber,
3000, Notification.Position.BOTTOM_CENTER);
if (hoursWorked != null) {
hoursWorked.setWeekNumber(weekNumber);
}
}
});
}
private void saveHoursWorked() {
if (isFormValid()) {
HoursWorked hoursWorked = getEntity();
String actividad = activityField.getValue();
String tareaEspecifica = tareasEspecificasDropdown.getValue();
if (actividad != null && !actividad.isEmpty() && tareaEspecifica != null) {
Notification.show("Solo puedes elegir una: actividad del proyecto o tarea de la empresa.",
3000, Notification.Position.BOTTOM_CENTER);
return;
}
if (actividad != null && !actividad.isEmpty()) {
hoursWorked.setActividad(actividad);
} else if (tareaEspecifica != null) {
if ("Otros".equals(tareaEspecifica)) {
// Validar que se ingresó una tarea específica en el campo de texto
String tareaEspecificaInputValue = tareaEspecificaInput.getValue();
if (tareaEspecificaInputValue == null || tareaEspecificaInputValue.isEmpty()) {
Notification.show("Por favor, ingresa una tarea específica.",
3000, Notification.Position.BOTTOM_CENTER);
return;
}
hoursWorked.setTareaEspecifica(tareaEspecificaInputValue);
} else {
hoursWorked.setTareaEspecifica(tareaEspecifica);
}
} else {
Notification.show("Por favor, selecciona una actividad o tarea para guardar.",
3000, Notification.Position.BOTTOM_CENTER);
return;
}
setFieldValues(hoursWorked);
hoursWorkedService.save(hoursWorked);
Notification.show("Horas trabajadas guardadas correctamente.",
3000, Notification.Position.BOTTOM_CENTER);
closeForm();
}
}
private void setFieldValues(final HoursWorked hoursWorked) {
hoursWorked.setDate(dateField.getValue());
hoursWorked.setTeam(teamField.getValue());
hoursWorked.setEmployee(employeeField.getValue());
hoursWorked.setActividad(activityField.getValue());
try {
double hours = Double.parseDouble(hoursField.getValue());
hoursWorked.setHours(hours);
} catch (NumberFormatException e) {
Notification.show("Por favor, ingrese un número válido para las horas.");
}
if ("Otros".equals(tareasEspecificasDropdown.getValue())) {
hoursWorked.setActividad(tareaEspecificaInput.getValue());
try {
double horasEspecifica = Double.parseDouble(horasTareaEspecificaField.getValue());
hoursWorked.setHours(horasEspecifica);
double totalHoras = hoursWorked.getHours() + horasEspecifica;
hoursWorked.setTotalHours(totalHoras);
} catch (NumberFormatException e) {
Notification.show("Por favor, ingrese un número válido para las horas de la tarea específica.");
}
}
}
private void closeForm() {
if (hoursWorked != null) {
getUI().ifPresent(ui -> ui.navigate("hours-worked-list/" + hoursWorked.getId().toString()));
} else {
getUI().ifPresent(ui -> ui.navigate("hours-worked-list"));
}
}
private boolean isFormValid() {
boolean isTareaEspecificaValida = "Otros".equals(tareasEspecificasDropdown.getValue())
? !tareaEspecificaInput.isEmpty()
: tareasEspecificasDropdown.getValue() != null;
boolean isActividadValida = !activityField.isEmpty();
boolean isSoloUnaOpcionElegida = (isActividadValida && tareasEspecificasDropdown.isEmpty())
|| (!isActividadValida && isTareaEspecificaValida);
return dateField.getValue() != null
&& teamField.getValue() != null
&& employeeField.getValue() != null
&& isSoloUnaOpcionElegida;
}
private void configureViewOrEditAction(final String action) {
if ("edit".equals(action) && hoursWorked != null) {
setFieldsReadOnly(false);
} else if ("view".equals(action) && hoursWorked != null) {
setFieldsReadOnly(true);
saveButton.setEnabled(false);
}
}
private void setFieldsReadOnly(final boolean readOnly) {
dateField.setReadOnly(readOnly);
teamField.setReadOnly(readOnly);
employeeField.setReadOnly(readOnly);
activityField.setReadOnly(readOnly);
}
}

View File

@ -1,18 +1,6 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.views.assessment.EvaluationsListView;
import com.primefactorsolutions.views.employee.DocumentsListView;
import com.primefactorsolutions.views.employee.EmployeesListView;
import com.primefactorsolutions.views.admin.TimeOffListView;
import com.primefactorsolutions.views.assessment.ExamsListView;
import com.primefactorsolutions.views.assessment.CandidatesListView;
import com.primefactorsolutions.views.assessment.QuestionsListView;
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.primefactorsolutions.views.util.AuthUtils;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.applayout.AppLayout;
@ -138,63 +126,48 @@ public class MainLayout extends AppLayout {
private SideNav createNavigation() {
final SideNav nav = new SideNav();
if (AuthUtils.isUser(authContext)) {
nav.addItem(new SideNavItem("Home", MainView.class, LineAwesomeIcon.HOME_SOLID.create()));
authContext.getAuthenticatedUser(UserDetails.class).ifPresent(u -> {
SideNavItem recruiting = new SideNavItem("Recruiting");
recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create());
recruiting.addItem(new SideNavItem("Assessments", AssessmentsListView.class,
LineAwesomeIcon.RIBBON_SOLID.create()));
recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class,
LineAwesomeIcon.USER.create()));
recruiting.addItem(new SideNavItem("Questions", QuestionsListView.class,
LineAwesomeIcon.QUESTION_SOLID.create()));
if (AuthUtils.isAdmin(authContext)) {
SideNavItem admin = new SideNavItem("Admin");
admin.setPrefixComponent(LineAwesomeIcon.BUILDING.create());
admin.addItem(new SideNavItem("Calendario", TimeOffListView.class,
LineAwesomeIcon.CALENDAR.create()));
nav.addItem(admin);
SideNavItem recruiting = new SideNavItem("Recruiting");
recruiting.setPrefixComponent(LineAwesomeIcon.BUSINESS_TIME_SOLID.create());
recruiting.addItem(new SideNavItem("Candidates", CandidatesListView.class,
LineAwesomeIcon.USER.create()));
recruiting.addItem(new SideNavItem("Evaluations", EvaluationsListView.class,
LineAwesomeIcon.BOOK_READER_SOLID.create()));
recruiting.addItem(new SideNavItem("Exams", ExamsListView.class,
LineAwesomeIcon.PEN_NIB_SOLID.create()));
recruiting.addItem(new SideNavItem("Questions", QuestionsListView.class,
LineAwesomeIcon.QUESTION_SOLID.create()));
nav.addItem(recruiting);
}
final SideNavItem timeOff = new SideNavItem("Time-off");
timeOff.setPrefixComponent(LineAwesomeIcon.PLANE_DEPARTURE_SOLID.create());
timeOff.addItem(new SideNavItem("Vacations", TimeOffSummaryListView.class,
LineAwesomeIcon.UMBRELLA_BEACH_SOLID.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());
timesheet.addItem(new SideNavItem("Registro de Horas Trabajadas", TimesheetListView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
if (AuthUtils.isAdmin(authContext)) {
timesheet.addItem(new SideNavItem("Reporte Horas Trabajadas", TimesheetReportView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
}
final SideNavItem profile = new SideNavItem("Employee");
profile.setPrefixComponent(LineAwesomeIcon.USER_TIE_SOLID.create());
if (AuthUtils.isAdmin(authContext)) {
profile.addItem(new SideNavItem("Profiles", EmployeesListView.class,
LineAwesomeIcon.USER_FRIENDS_SOLID.create()));
}
profile.addItem(new SideNavItem("My Profile", "/employees/me",
SideNavItem admin = new SideNavItem("Admin");
admin.setPrefixComponent(LineAwesomeIcon.BUILDING.create());
admin.addItem(new SideNavItem("Employees", EmployeesListView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create()));
profile.addItem(new SideNavItem("Documents", DocumentsListView.class,
admin.addItem(new SideNavItem("Documents", DocumentsListView.class,
LineAwesomeIcon.FILE_ALT_SOLID.create()));
SideNavItem timeOff = new SideNavItem("My Time-off", TimeoffView.class,
LineAwesomeIcon.PLANE_DEPARTURE_SOLID.create());
timeOff.addItem(new SideNavItem("Vacations", RequestsListView.class,
LineAwesomeIcon.UMBRELLA_BEACH_SOLID.create()));
timeOff.addItem(new SideNavItem("Add Vacation", RequestRegisterView.class,
LineAwesomeIcon.CALENDAR_PLUS.create()));
timeOff.addItem(new SideNavItem("Pending Requests", PendingRequestsListView.class,
LineAwesomeIcon.LIST_ALT.create()));
SideNavItem timesheet = new SideNavItem("My Timesheet", TimesheetView.class,
LineAwesomeIcon.HOURGLASS_START_SOLID.create());
timesheet.addItem(new SideNavItem("Registro de Horas Trabajadas", HoursWorkedListView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
timesheet.addItem(new SideNavItem("Reporte Horas Trabajadas", ReporteView.class,
LineAwesomeIcon.ID_CARD_SOLID.create()));
SideNavItem profile = new SideNavItem("My Profile", ProfileView.class,
LineAwesomeIcon.USER_EDIT_SOLID.create());
nav.addItem(new SideNavItem("Home", MainView.class, LineAwesomeIcon.HOME_SOLID.create()));
nav.addItem(admin);
nav.addItem(recruiting);
nav.addItem(profile);
nav.addItem(timesheet);
nav.addItem(timeOff);
}
});
return nav;
}

View File

@ -2,7 +2,6 @@ package com.primefactorsolutions.views;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import jakarta.annotation.security.PermitAll;
@ -13,6 +12,6 @@ import jakarta.annotation.security.PermitAll;
public class MainView extends Main {
public MainView() {
add(new VerticalLayout(new Text("Welcome to PFS!")));
add(new Text("Welcome"));
}
}

View File

@ -0,0 +1,224 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Pending Requests")
@Route(value = "/pending-requests", layout = MainLayout.class)
@PermitAll
public class PendingRequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimeOffRequest> pendingRequestsGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<TimeOffRequestType> categoryFilter;
public PendingRequestsListView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
refreshGeneralPendingRequestsGrid(null, null, null);
}
private void initializeView() {
setupFilters();
setupPendingRequestsGrid();
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
hl.add(createCategoryFilter());
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 -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem approveItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.CHECK, "Aprobar");
approveItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
actionForRequest(timeOffRequest.getId(), TimeOffRequestStatus.APROBADO);
});
final MenuItem rejectItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.BAN, "Rechazar");
rejectItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
actionForRequest(timeOffRequest.getId(), TimeOffRequestStatus.RECHAZADO);
});
return menuBar;
});
pendingRequestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
pendingRequestsGrid.setPageSize(PAGE_SIZE);
getCurrentPageLayout().add(pendingRequestsGrid);
}
private void actionForRequest(final UUID selectedRequestId, final TimeOffRequestStatus status) {
TimeOffRequest request = requestService.findTimeOffRequest(selectedRequestId);
request.setState(status);
requestService.saveTimeOffRequest(request);
refreshGeneralPendingRequestsGrid(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);
});
pendingRequestsGrid.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);
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"TODOS".equals(team.getName())) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getEmployee().getTeam() != null
&& emp.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null && category != TimeOffRequestType.TODOS) {
filteredPendingRequests = filteredPendingRequests.stream()
.filter(emp -> emp.getCategory().equals(category))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredPendingRequests.size());
return filteredPendingRequests.subList(start, end);
}
private String getEmployeeFullName(final TimeOffRequest request) {
Employee employee = request.getEmployee();
return getEmployeeFullNameLabel(employee);
}
private String getEmployeeFullNameLabel(final Employee employee) {
return "TODOS".equals(employee.getFirstName())
? "TODOS" : employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamName(final TimeOffRequest request) {
Team team = request.getEmployee().getTeam();
return team != null ? team.getName() : "Sin asignar";
}
private String getTeamLabel(final Team team) {
return "TODOS".equals(team.getName()) ? "TODOS" : team.getName();
}
private String getCategory(final TimeOffRequest request) {
return String.valueOf(request.getCategory());
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullNameLabel);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Categoría");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event ->
refreshGeneralPendingRequestsGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue()
)
);
return categoryFilter;
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
}

View File

@ -0,0 +1,17 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Profile")
@Route(value = "/profiles", layout = MainLayout.class)
public class ProfileView extends Main {
}

View File

@ -1,29 +1,27 @@
package com.primefactorsolutions.views.assessment;
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.QuestionService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Questions")
@PageTitle("Assessments")
@Route(value = "/questions", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class QuestionView extends BaseEntityForm<Question> implements HasUrlParameter<String> {
@PermitAll
public class QuestionView extends BeanValidationForm<Question> implements HasUrlParameter<String> {
private final QuestionService questionService;
private TextField title = null;
@ -31,9 +29,8 @@ public class QuestionView extends BaseEntityForm<Question> implements HasUrlPara
private TextArea content = null;
private IntegerField timeMinutes = null;
public QuestionView(final AuthenticationContext authenticationContext,
final QuestionService questionService) {
super(authenticationContext, Question.class);
public QuestionView(final QuestionService questionService) {
super(Question.class);
this.questionService = questionService;
title = new TextField();
title.setWidthFull();
@ -51,8 +48,8 @@ public class QuestionView extends BaseEntityForm<Question> implements HasUrlPara
content.setLabel("Content");
setSavedHandler((SavedHandler<Question>) question -> {
questionService.createOrUpdate(question);
goTo(QuestionsListView.class);
final Question saved = questionService.createOrUpdate(question);
setEntityWithEnabledSave(saved);
});
}

View File

@ -0,0 +1,86 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.QuestionService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.DataProvider;
import com.vaadin.flow.data.provider.DataProviderListener;
import com.vaadin.flow.data.provider.Query;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.shared.Registration;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Questions")
@Route(value = "/questions", layout = MainLayout.class)
@PermitAll
public class QuestionsListView extends BaseView {
public QuestionsListView(final QuestionService questionService) {
final HorizontalLayout hl = new HorizontalLayout();
final Button addQuestion = new Button("Add Question");
addQuestion.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, "new")));
hl.add(addQuestion);
final VGrid<Question> grid = new VGrid<>(Question.class);
grid.setColumns("id", "title");
grid.addComponentColumn((ValueProvider<Question, Component>) question -> {
final Button edit = new Button("Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, question.getId().toString())));
return edit;
});
grid.setDataProvider(new DataProvider<>() {
@Override
public boolean isInMemory() {
return false;
}
@Override
public int size(final Query<Question, Object> query) {
return questionService.getQuestions().size();
}
@SuppressWarnings("unused")
@Override
public Stream<Question> fetch(final Query<Question, Object> query) {
int limit = query.getLimit();
int pagerSize = query.getPageSize();
int page = query.getPage();
return questionService.getQuestions().stream();
}
@Override
public void refreshItem(final Question question) {
// no-op
}
@Override
public void refreshAll() {
// no-op
}
@Override
public Registration addDataProviderListener(final DataProviderListener<Question> dataProviderListener) {
return null;
}
});
grid.setAllRowsVisible(true);
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -0,0 +1,227 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.HoursWorked;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.service.HoursWorkedService;
import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Span;
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.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import org.springframework.beans.factory.annotation.Autowired;
import com.primefactorsolutions.service.EmployeeService;
import java.io.ByteArrayInputStream;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.TextStyle;
import java.time.temporal.WeekFields;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
@PermitAll
@Route(value = "/reportes", layout = MainLayout.class)
@PageTitle("Reporte de Horas Trabajadas")
public class ReporteView extends BaseView {
private final EmployeeService employeeService;
private final HoursWorkedService hoursWorkedService;
private final ReportService reportService;
private final TeamService teamService;
private final ComboBox<Team> equipoComboBox = new ComboBox<>("Seleccionar Equipo");
private final ComboBox<String> semanaComboBox = new ComboBox<>("Seleccionar Semana");
private final Grid<Map<String, Object>> grid = new Grid<>();
private final VerticalLayout headerLayout = new VerticalLayout();
private Anchor downloadLink;
private final Span semanaInfoSpan = new Span();
// Obtener el año actual
private final int currentYear = LocalDate.now().getYear();
@Autowired
public ReporteView(final HoursWorkedService hoursWorkedService,
final ReportService reportService, final TeamService teamService,
final EmployeeService employeeService) {
this.hoursWorkedService = hoursWorkedService;
this.reportService = reportService;
this.teamService = teamService;
this.employeeService = employeeService;
H2 title = new H2("Reporte de Horas Trabajadas");
getCurrentPageLayout().add(title);
List<Team> teams = teamService.findAllTeams();
equipoComboBox.setItems(teams);
equipoComboBox.setItemLabelGenerator(Team::getName);
// Configurar el ComboBox de semanas
initializeSemanaComboBox();
// Listener para actualizar `semanaInfoSpan` con la selección del usuario en `semanaComboBox`
semanaComboBox.addValueChangeListener(event -> {
String selectedWeek = event.getValue();
semanaInfoSpan.setText(selectedWeek != null ? selectedWeek : "Selecciona una semana");
});
Button reportButton = new Button("Generar Reporte de Horas Trabajadas",
event -> generateHoursWorkedReport());
getCurrentPageLayout().add(reportButton);
HorizontalLayout filtersLayout = new HorizontalLayout(equipoComboBox, semanaComboBox);
getCurrentPageLayout().add(filtersLayout);
getCurrentPageLayout().add(headerLayout);
updateHeaderLayout(null, null);
grid.addColumn(map -> map.get("Empleado")).setHeader("Empleado");
grid.addColumn(map -> map.get("Horas Trabajadas")).setHeader("Horas Trabajadas");
grid.addColumn(map -> map.get("Horas Pendientes")).setHeader("Horas Pendientes");
grid.addColumn(map -> map.get("Observaciones")).setHeader("Observaciones");
getCurrentPageLayout().add(grid);
}
private void initializeSemanaComboBox() {
int year = LocalDate.now().getYear();
LocalDate startOfYear = LocalDate.of(year, 1, 5); // Suponemos que la semana comienza el 5 de enero.
List<String> semanas = startOfYear.datesUntil(LocalDate.of(year + 1, 1, 1),
java.time.Period.ofWeeks(1))
.map(date -> {
int weekNumber = date.get(WeekFields.of(DayOfWeek.MONDAY, 1)
.weekOfWeekBasedYear());
LocalDate endOfWeek = date.plusDays(6);
return String.format("Semana %d: %s - %s",
weekNumber,
date.getDayOfMonth() + " de " + date.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault()),
endOfWeek.getDayOfMonth() + " de " + endOfWeek.getMonth()
.getDisplayName(TextStyle.FULL, Locale.getDefault())
);
})
.collect(Collectors.toList());
semanaComboBox.setItems(semanas);
semanaComboBox.setPlaceholder("Seleccione una semana");
}
private void generateHoursWorkedReport() {
Team selectedEquipo = equipoComboBox.getValue();
String selectedWeek = semanaComboBox.getValue();
if (selectedEquipo == null || selectedWeek == null) {
Notification.show("Por favor, selecciona un equipo y una semana para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
int weekNumber = Integer.parseInt(selectedWeek.split(" ")[1].replace(":", ""));
LocalDate selectedDate = LocalDate.now().with(WeekFields.of(DayOfWeek.FRIDAY, 1)
.weekOfWeekBasedYear(), weekNumber);
updateHeaderLayout(selectedEquipo, selectedDate);
List<HoursWorked> hoursWorkedList = hoursWorkedService.findAll().stream()
.filter(hw -> hw.getEmployee().getTeam().getId().equals(selectedEquipo
.getId()) && hw.getWeekNumber() == weekNumber)
.collect(Collectors.toList());
System.out.println(hoursWorkedList);
if (hoursWorkedList.isEmpty()) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
List<Map<String, Object>> data = hoursWorkedList.stream()
.map(hoursWorked -> {
Map<String, Object> map = new HashMap<>();
map.put("ID", hoursWorked.getId().toString());
map.put("Employee ID", hoursWorked.getEmployee().getId().toString());
map.put("Empleado", hoursWorked.getEmployee().getFirstName() + " "
+ hoursWorked.getEmployee().getLastName());
map.put("Horas Trabajadas", hoursWorked.getTotalHours());
map.put("Horas Pendientes", 40 - hoursWorked.getTotalHours());
map.put("Observaciones", "");
return map;
})
.collect(Collectors.toList());
grid.setItems(data);
generateExcelDownloadLink(data, weekNumber);
}
private void updateHeaderLayout(final Team team, final LocalDate dateInWeek) {
headerLayout.removeAll();
if (team != null && dateInWeek != null) {
int weekNumber = getWeekOfYear(dateInWeek);
headerLayout.add(new Span("Informe "
+ String.format("%03d", weekNumber) + "/" + currentYear) {{
getStyle().set("font-size", "24px");
getStyle().set("font-weight", "bold");
}});
String teamLeadName = employeeService.getTeamLeadName(team.getId());
headerLayout.add(
new Span("Asunto: Informe Semanal de Horas Trabajadas") {{
getStyle().set("font-size", "18px");
}},
semanaInfoSpan,
new Span("Horas a cumplir: 40 horas") {{
getStyle().set("font-size", "18px");
}},
new Span("Equipo: " + team.getName()) {{
getStyle().set("font-size", "18px");
}},
new Span("Team Lead: " + teamLeadName) {{
getStyle().set("font-size", "18px");
}}
);
}
}
private void generateExcelDownloadLink(final List<Map<String, Object>> data, final int weekNumber) {
try {
List<String> headers = List.of("Empleado",
"Horas Trabajadas", "Horas Pendientes", "Observaciones");
String selectedTeam = equipoComboBox.getValue().getName();
byte[] excelBytes = reportService.writeAsExcel(
"hours_worked_report", headers, data, selectedTeam, weekNumber, currentYear);
StreamResource excelResource = new StreamResource("hours_worked_report.xlsx",
() -> new ByteArrayInputStream(excelBytes));
if (downloadLink == null) {
downloadLink = new Anchor(excelResource, "Descargar Reporte en Excel");
downloadLink.getElement().setAttribute("download", true);
getCurrentPageLayout().add(downloadLink);
} else {
downloadLink.setHref(excelResource);
}
} catch (Exception e) {
Notification.show("Error al generar el reporte de horas trabajadas en Excel.",
3000, Notification.Position.MIDDLE);
}
}
private int getWeekOfYear(final LocalDate date) {
return date.get(WeekFields.of(Locale.getDefault()).weekOfWeekBasedYear());
}
}

View File

@ -0,0 +1,415 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.html.H3;
import com.vaadin.flow.component.html.Span;
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.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
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
@PermitAll
@Scope("prototype")
@PageTitle("Employee Request")
@Route(value = "/requests", layout = MainLayout.class)
public class RequestEmployeeView extends BaseView implements HasUrlParameter<String> {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final VacationService vacationService;
private final PagingGrid<TimeOffRequest> requestGrid = new PagingGrid<>(TimeOffRequest.class);
private List<TimeOffRequest> requests = Collections.emptyList();
private ComboBox<TimeOffRequestType> categoryFilter;
private ComboBox<TimeOffRequestStatus> stateFilter;
private UUID employeeId;
public RequestEmployeeView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.vacationService = vacationService;
}
private void initializeView() {
requestService.updateRequestStatuses();
setupFilters();
setupGrid();
getCurrentPageLayout().add(requestGrid, new H3("Balance"), createSummaryLayout());
refreshRequestGrid(null, null);
}
private void setupFilters() {
categoryFilter = createCategoryFilter();
stateFilter = createStateFilter();
HorizontalLayout hl = new HorizontalLayout(categoryFilter, stateFilter);
getCurrentPageLayout().add(hl);
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter = new ComboBox<>("Categoría");
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.setValue(TimeOffRequestType.values()[0]);
categoryFilter.addValueChangeListener(event -> refreshRequestGrid(event.getValue(), stateFilter.getValue()));
return categoryFilter;
}
private ComboBox<TimeOffRequestStatus> createStateFilter() {
stateFilter = new ComboBox<>("Estado de la solicitud");
stateFilter.setItems(TimeOffRequestStatus.values());
stateFilter.setValue(TimeOffRequestStatus.values()[0]);
stateFilter.addValueChangeListener(event -> refreshRequestGrid(categoryFilter.getValue(), event.getValue()));
return stateFilter;
}
private void setupGrid() {
requestGrid.setColumns(
"category",
"state",
"startDate",
"endDate",
"daysToBeTake");
requestGrid.getColumnByKey("category").setHeader("Categoría");
requestGrid.getColumnByKey("state").setHeader("Estado");
requestGrid.getColumnByKey("startDate").setHeader("Fecha de Inicio");
requestGrid.getColumnByKey("endDate").setHeader("Fecha de Fin");
requestGrid.getColumnByKey("daysToBeTake").setHeader("Días a Tomar");
requestGrid.addComponentColumn((ValueProvider<TimeOffRequest, Component>) timeOffRequest -> {
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 ->
navigateToViewRequest(timeOffRequest));
final MenuItem edit = createIconItem(menuBar, VaadinIcon.PENCIL, "Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
navigateToEditRequest(timeOffRequest));
return menuBar;
});
requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestGrid.setPageSize(PAGE_SIZE);
}
private Set<TimeOffRequestType> getStandardExclusions() {
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> getMaleSpecificExclusions() {
return Set.of(
TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL,
TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL
);
}
private VerticalLayout createSummaryLayout() {
Employee employee = employeeService.getEmployee(employeeId);
boolean isMale = employee.getGender() == Employee.Gender.MALE;
int currentYear = LocalDate.now().getYear();
List<Vacation> vacations = vacationService.findVacations();
double healthLicence = 2;
List<TimeOffRequest> healthRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD);
if (healthRequests != null && !healthRequests.isEmpty()) {
healthLicence = healthRequests.getLast().getDaysBalance();
}
double totalFixedAndMovableHolidays = calculateHolidayDays(vacations);
double totalPersonalDays = calculatePersonalDays(vacations, isMale);
List<Double> vacationDays = calculateVacationDays(employee);
double utilizedVacationCurrentDays = vacationDays.get(1);
List<TimeOffRequest> vacationCurrentRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ACTUAL);
if (vacationCurrentRequests != null && !vacationCurrentRequests.isEmpty()) {
utilizedVacationCurrentDays = vacationCurrentRequests.getLast().getDaysBalance();
}
double totalVacationCurrentDays = vacationDays.get(1) - (vacationDays.get(1) - utilizedVacationCurrentDays);
double utilizedVacationPreviousDays = vacationDays.get(0);
List<TimeOffRequest> vacationPreviousRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.VACACION_GESTION_ANTERIOR);
if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) {
utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance();
}
double totalVacationPreviousDays = vacationDays.getFirst()
- (vacationDays.getFirst() - utilizedVacationPreviousDays);
double utilizedFixedAndMovableHolidays = calculateHolidayUtilizedDays(currentYear);
double utilizedPersonalDays = calculatePersonalDaysUtilized(isMale, currentYear);
double remainingHolidayDays = totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays;
double remainingPersonalDays = (totalPersonalDays - utilizedPersonalDays) + healthLicence;
double remainingVacationDays = totalVacationCurrentDays + totalVacationPreviousDays;
double totalAvailableDays = remainingHolidayDays + remainingPersonalDays + remainingVacationDays;
return new VerticalLayout(
new Span("Total feriados fijos y movibles: " + remainingHolidayDays),
new Span("Total días libres personales: " + remainingPersonalDays),
new Span("Total vacaciones pendientes de uso: " + remainingVacationDays),
new Span("TOTAL GENERAL DE DÍAS DISPONIBLES: " + totalAvailableDays)
);
}
private double calculateHolidayDays(final List<Vacation> vacations) {
return vacations.stream()
.filter(req -> req.getType() != Vacation.Type.OTHER)
.mapToDouble(Vacation::getDuration)
.sum();
}
private double calculatePersonalDays(final List<Vacation> vacations, final boolean isMale) {
return vacations.stream()
.filter(req -> req.getType() == Vacation.Type.OTHER)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.mapToDouble(Vacation::getDuration)
.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 calculateHolidayUtilizedDays(final int year) {
return requests.stream()
.filter(this::verificationIsHoliday)
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private double calculatePersonalDaysUtilized(final boolean isMale, final int year) {
return requests.stream()
.filter(req -> !verificationIsHoliday(req))
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private int getStartDateYear(final TimeOffRequest request) {
if (request.getStartDate() != null) {
return request.getStartDate().getYear();
}
return 0;
}
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 verificationIsHoliday(final TimeOffRequest request) {
Vacation vacation = vacationService.findVacationByCategory(request.getCategory());
return vacation.getType() != Vacation.Type.OTHER;
}
private void navigateToRequestsListView() {
getUI().ifPresent(ui -> ui.navigate(RequestsListView.class));
}
private void navigateToEditRequest(final TimeOffRequest request) {
navigateToRequestView(request, "edit");
}
private void navigateToViewRequest(final TimeOffRequest request) {
navigateToRequestView(request, "view");
}
private void navigateToRequestView(final TimeOffRequest request, final String action) {
getUI().ifPresent(ui -> ui.navigate(RequestView.class, request.getId().toString() + "/" + action));
}
private void refreshRequestGrid(final TimeOffRequestType category, final TimeOffRequestStatus state) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestGrid.getPageSize());
return fetchFilteredTimeOffRequests(start, pageSize, category, state);
});
requestGrid.getDataProvider().refreshAll();
}
private List<TimeOffRequest> fetchFilteredTimeOffRequests(final int start,
final int pageSize,
final TimeOffRequestType category,
final TimeOffRequestStatus state) {
requests = requestService.findRequestsByEmployeeId(employeeId);
generateRequests();
if (category != null && !"TODOS".equals(category.name())) {
requests = requests.stream()
.filter(req -> req.getCategory().equals(category))
.collect(Collectors.toList());
}
if (state != null && !"TODOS".equals(state.name())) {
requests = requests.stream()
.filter(req -> req.getState().equals(state))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, requests.size());
return requests.subList(start, end);
}
public void generateRequests() {
boolean isMale = isEmployeeMale();
for (TimeOffRequestType type : TimeOffRequestType.values()) {
if (shouldIncludeRequest(type) && isValidRequestType(type, isMale)) {
TimeOffRequest request = createRequest(type);
if (isVacationExpired(request)) {
request.setState(TimeOffRequestStatus.VENCIDO);
} else {
request.setState(TimeOffRequestStatus.PENDIENTE);
}
requests.add(request);
}
}
}
private boolean isEmployeeMale() {
return employeeService.getEmployee(employeeId).getGender() == Employee.Gender.MALE;
}
private boolean isValidRequestType(final TimeOffRequestType type, final boolean isMale) {
return !getStandardExclusions().contains(type)
&& !(isMale && getMaleSpecificExclusions().contains(type))
&& type != TimeOffRequestType.TODOS;
}
private TimeOffRequest createRequest(final TimeOffRequestType type) {
TimeOffRequest request = new TimeOffRequest();
request.setCategory(type);
return request;
}
private boolean isVacationExpired(final TimeOffRequest request) {
Vacation vacation = vacationService.findVacationByCategory(request.getCategory());
if (vacation != null && vacation.getMonthOfYear() != null && vacation.getDayOfMonth() != null) {
int vacationMonth = vacation.getMonthOfYear();
int vacationDay = vacation.getDayOfMonth();
int currentMonth = LocalDate.now().getMonthValue();
int currentDay = LocalDate.now().getDayOfMonth();
return vacationMonth < currentMonth || (vacationMonth == currentMonth && vacationDay < currentDay);
}
return false;
}
private boolean shouldIncludeRequest(final TimeOffRequestType type) {
List<TimeOffRequest> existingRequest = requestService.findByEmployeeAndCategory(employeeId, type);
return existingRequest.isEmpty();
}
@Override
public void setParameter(final BeforeEvent event, final String parameter) {
employeeId = UUID.fromString(parameter);
Employee employee = employeeService.getEmployee(employeeId);
requests = requestService.findRequestsByEmployeeId(employeeId);
setViewTitle(employee.getFirstName() + " " + employee.getLastName(), employee.getTeam().getName());
requestGrid.setItems(requests);
initializeView();
}
private void setViewTitle(final String employeeName, final String employeeTeam) {
getCurrentPageLayout().addComponentAsFirst(new H3(String.format("%s (%s)", employeeName, employeeTeam)));
}
}

View File

@ -0,0 +1,511 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
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.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 = "/requests/new", layout = MainLayout.class)
public class RequestRegisterView 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 VacationService vacationService;
private final Binder<TimeOffRequest> binder;
private Vacation vacation;
private Employee employee;
private LocalDate endDate;
private Button saveButton;
private Button closeButton;
public RequestRegisterView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.vacationService = vacationService;
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 -> updateDatePickerMinValues());
endDatePicker.addValueChangeListener(event -> 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 -> category != TimeOffRequestType.TODOS)
.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) {
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) {
vacation = vacationService.findVacationByCategory(selectedCategory);
UUID employeeId = employeeComboBox.getValue().getId();
List<TimeOffRequest> requests = requestService.findByEmployeeAndCategory(employeeId, selectedCategory);
if (vacation != 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(vacation.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(vacation.getDuration());
}
setDatePickerLimits(vacation);
}
}
private void setDatePickerLimits(final Vacation vacation) {
LocalDate startDate;
endDate = null;
UUID employeeId = employee.getId();
List<TimeOffRequest> previousRequests
= requestService.findByEmployeeAndCategory(employeeId, vacation.getCategory());
int startYear = calculateStartYear(previousRequests);
startDate = determineStartDate(vacation, startYear);
if (startDate.isBefore(LocalDate.now())) {
startDate = determineStartDate(vacation, startYear + 1);
}
if (startDate != null) {
if (vacation.getExpiration() != null) {
endDate = startDate.plusDays(vacation.getExpiration().intValue() - 1);
} else {
endDate = LocalDate.of(startDate.getYear(), 12, 31);
}
} else {
startDate = LocalDate.now();
}
setPickerValues(vacation, 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 Vacation vacation, final int startYear) {
if (vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS && employee.getBirthday() != null) {
return LocalDate.of(startYear, employee.getBirthday().getMonth(), employee.getBirthday().getDayOfMonth());
}
if (vacation.getMonthOfYear() != null && vacation.getDayOfMonth() != null) {
return LocalDate.of(startYear, vacation.getMonthOfYear().intValue(), vacation.getDayOfMonth().intValue());
}
if (vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD) {
return LocalDate.now();
}
return LocalDate.now();
}
private void setPickerValues(final Vacation vacation, final LocalDate startDate) {
startDatePicker.setValue(startDate);
if ((vacation.getDuration() != null && vacation.getDuration() == 0.5)
|| vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS) {
endDatePicker.setValue(startDate);
} else {
int durationDays = (vacation.getDuration() != null ? vacation.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)) {
double daysToBeTaken = calculateDaysBetween(startDate, endDate);
setDaysToBeTakenField(daysToBeTaken);
double balanceDays = calculateBalanceDays(availableDays, daysToBeTakenField.getValue());
balanceDaysField.setValue(balanceDays);
if (balanceDays < 0.0) {
clearFields();
}
}
}
private boolean areDatesValid(final LocalDate startDate, final LocalDate endDate) {
return startDate != null && endDate != null;
}
private double calculateDaysBetween(final LocalDate startDate, final LocalDate endDate) {
return java.time.temporal.ChronoUnit.DAYS.between(startDate, endDate) + 1;
}
private void setDaysToBeTakenField(final double daysToBeTaken) {
if (vacation.getCategory() == TimeOffRequestType.PERMISOS_DE_SALUD
|| vacation.getCategory() == TimeOffRequestType.CUMPLEAÑOS
|| vacation.getCategory() == TimeOffRequestType.DIA_DEL_PADRE
|| vacation.getCategory() == TimeOffRequestType.DIA_DE_LA_MADRE
|| vacation.getCategory() == TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL
|| vacation.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);
}
requestService.saveTimeOffRequest(request);
Notification.show("Solicitud guardada correctamente.");
closeForm();
}
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(RequestsListView.class));
}
}

View File

@ -0,0 +1,160 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.vaadin.flow.component.Component;
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.textfield.NumberField;
import com.vaadin.flow.router.*;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.form.BeanValidationForm;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Request")
@Route(value = "/requests/:requestId?/:action?", layout = MainLayout.class)
public class RequestView extends BeanValidationForm<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 TimeOffRequestService requestService;
private final EmployeeService employeeService;
private TimeOffRequest request;
private Employee employee;
private Button saveButton;
public RequestView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService) {
super(TimeOffRequest.class);
this.requestService = requestService;
this.employeeService = employeeService;
state.setItems(List.of(TimeOffRequestStatus.values()));
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String requestIdString = params.get("requestId").orElse(null);
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);
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(
createEmployeeHeader(),
createTeamHeader(),
createCategoryHeader(),
state,
expiration,
startDate,
endDate,
availableDays,
daysToBeTake,
createCloseButton()
);
}
protected Button createSaveButton() {
saveButton = new Button("Guardar");
saveButton.addClickListener(event -> saveRequest());
return saveButton;
}
protected Button createCloseButton() {
Button closeButton = new Button("Salir");
closeButton.addClickListener(event -> closeForm());
return closeButton;
}
private void setFieldsReadOnly(final boolean option) {
state.setReadOnly(option);
expiration.setReadOnly(option);
startDate.setReadOnly(option);
endDate.setReadOnly(option);
availableDays.setReadOnly(option);
daysToBeTake.setReadOnly(option);
}
private void saveRequest() {
if (isFormValid()) {
TimeOffRequest request = getEntity();
setRequestFieldValues(request);
requestService.saveTimeOffRequest(request);
Notification.show("Solicitud guardada correctamente.");
closeForm();
}
}
private void setRequestFieldValues(final TimeOffRequest request) {
request.setState(state.getValue());
request.setExpiration(expiration.getValue());
request.setStartDate(startDate.getValue());
request.setEndDate(endDate.getValue());
request.setAvailableDays(availableDays.getValue());
request.setDaysToBeTake(daysToBeTake.getValue());
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate("requests/" + employee.getId().toString()));
}
private boolean isFormValid() {
return !state.isEmpty()
&& expiration.getValue() != null
&& startDate.getValue() != null
&& endDate.getValue() != null
&& availableDays.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);
saveButton.setEnabled(false);
}
}
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

@ -0,0 +1,408 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.service.TimeOffRequestService;
import com.primefactorsolutions.service.VacationService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.PagingGrid;
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 = "/requests", layout = MainLayout.class)
@PermitAll
public class RequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final VacationService vacationService;
private final PagingGrid<Employee> requestGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<Status> stateFilter;
public RequestsListView(final TimeOffRequestService requestService,
final EmployeeService employeeService,
final TeamService teamService,
final VacationService vacationService) {
this.requestService = requestService;
this.employeeService = employeeService;
this.teamService = teamService;
this.vacationService = vacationService;
initializeView();
refreshGeneralRequestGrid(null, null, null);
}
private void initializeView() {
requestService.updateRequestStatuses();
setupFilters();
setupRequestGrid();
getCurrentPageLayout().add(requestGrid);
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
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::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 Status state) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestGrid.getPageSize());
return fetchFilteredEmployees(start, pageSize, employee, team, state);
});
requestGrid.getDataProvider().refreshAll();
}
private List<Employee> fetchFilteredEmployees(final int start,
final int pageSize,
final Employee employee,
final Team team,
final Status state) {
List<Employee> filteredEmployees = employeeService.findAllEmployees();
if (employee != null && !"TODOS".equals(employee.getFirstName())) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null && !"TODOS".equals(team.getName())) {
filteredEmployees = filteredEmployees.stream()
.filter(emp -> emp.getTeam() != null && emp.getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (state != null && state != Status.TODOS) {
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 "TODOS".equals(employee.getFirstName())
? "TODOS" : 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 "TODOS".equals(team.getName()) ? "TODOS" : team.getName();
}
private String getEmployeeStatus(final Employee employee) {
Optional<TimeOffRequest> activeRequest = requestService
.findByEmployeeAndState(employee.getId(), TimeOffRequestStatus.EN_USO);
return activeRequest.isPresent() ? "EN_DESCANSO" : "ACTIVO";
}
private String getGeneralTotal(final Employee employee) {
List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
List<Vacation> vacations = vacationService.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(vacations, employeeRequests, employee);
double generalTotal = totalAvailable + totalVacations - totalUtilized;
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.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<Vacation> vacations, 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 = vacations.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 Vacation vacation,
final Set<TimeOffRequestType> excludedCategories,
final Set<TimeOffRequestType> genderSpecificExclusions,
final Employee employee,
final Set<TimeOffRequestType> employeeRequestCategories) {
if (excludedCategories.contains(vacation.getCategory())
&& !employeeRequestCategories.contains(vacation.getCategory())) {
return false;
}
return isFemale(employee) || !genderSpecificExclusions.contains(vacation.getCategory());
}
private boolean isFemale(final Employee employee) {
return employee.getGender() == Employee.Gender.FEMALE;
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employees.addFirst(createAllEmployeesOption());
employeeFilter.setItems(employees);
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.setValue(employees.getFirst());
employeeFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
event.getValue(),
teamFilter.getValue(),
stateFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teams.addFirst(createAllTeamsOption());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.setValue(teams.getFirst());
teamFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
employeeFilter.getValue(),
event.getValue(),
stateFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<Status> createStateFilter() {
stateFilter = new ComboBox<>("Estado del empleado");
stateFilter.setItems(Status.values());
stateFilter.setValue(Status.values()[0]);
stateFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue()
)
);
return stateFilter;
}
private enum Status {
TODOS,
EN_DESCANSO,
ACTIVO
}
private Employee createAllEmployeesOption() {
Employee allEmployeesOption = new Employee();
allEmployeesOption.setFirstName("TODOS");
return allEmployeesOption;
}
private Team createAllTeamsOption() {
Team allTeamsOption = new Team();
allTeamsOption.setName("TODOS");
return allTeamsOption;
}
private void navigateToMainView() {
getUI().ifPresent(ui -> ui.navigate(MainView.class));
}
private void navigateToTimeOffRequestView(final UUID idEmployee) {
getUI().ifPresent(ui -> ui.navigate("requests/" + idEmployee.toString()));
}
}

View File

@ -3,9 +3,9 @@ package com.primefactorsolutions.views;
import com.hilerio.ace.AceEditor;
import com.hilerio.ace.AceMode;
import com.hilerio.ace.AceTheme;
import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.model.Submission;
import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.service.AssessmentService;
import com.primefactorsolutions.service.CompilerService;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
@ -20,9 +20,9 @@ import com.vaadin.flow.component.menubar.MenuBarVariant;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.*;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.theme.lumo.LumoUtility.*;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Scope;
@ -37,17 +37,18 @@ import java.util.stream.Collectors;
@PageTitle("Evaluacion")
@SpringComponent
@Scope("prototype")
@RolesAllowed("ROLE_ADMIN")
@Route(value = "/submissions", layout = MainLayout.class)
@Route(value = "/submission", layout = MainLayout.class)
@AnonymousAllowed
@Slf4j
public class SubmissionView extends Main implements HasUrlParameter<String> {
private final CompilerService compilerService;
private final ExamService examService;
private final AssessmentService assessmentService;
private AceEditor questionEditor = null;
private AceEditor result = null;
private Dialog dialog = null;
private Exam exam = null;
private Assessment assessment = null;
private Submission currSubmission = null;
private MenuItem prev = null;
private MenuItem next = null;
@ -56,9 +57,9 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
private Section editorSection = null;
private H3 questionTitle = null;
public SubmissionView(final CompilerService compilerService, final ExamService examService) {
public SubmissionView(final CompilerService compilerService, final AssessmentService assessmentService) {
this.compilerService = compilerService;
this.examService = examService;
this.assessmentService = assessmentService;
addClassNames(Display.FLEX, Flex.GROW, Height.FULL);
@ -130,13 +131,13 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
prev = navMenuBar.addItem("Anterior pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
log.info(">>> prev");
this.currSubmission = this.examService.getPrevSubmission(exam.getId(),
this.currSubmission = this.assessmentService.getPrevSubmission(assessment.getId(),
this.currSubmission);
updateUI();
});
next = navMenuBar.addItem("Siguiente pregunta",
(ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
this.currSubmission.setText(this.questionEditor.getValue());
this.currSubmission.setResponse(this.questionEditor.getValue());
goToNext();
});
@ -185,7 +186,7 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
private void goToNext() {
log.info(">>> next");
Submission found = this.examService.getNextSubmission(exam.getId(),
Submission found = this.assessmentService.getNextSubmission(assessment.getId(),
this.currSubmission.getId(), false);
if (found != null) {
@ -208,25 +209,25 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
}
private void updateUI() {
if (exam == null || !exam.isStarted()) {
if (assessment == null || !assessment.isStarted()) {
editorSection.setVisible(false);
sidebar.setVisible(false);
} else {
if (currSubmission != null) {
questionEditor.setValue(this.currSubmission.getText());
questionEditor.setValue(this.currSubmission.getResponse());
questionTitle.setText(this.currSubmission.getQuestion().getTitle());
}
editorSection.setVisible(true);
sidebar.setVisible(true);
prev.setEnabled(currSubmission != null && !exam.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !exam.isLast(currSubmission));
prev.setEnabled(currSubmission != null && !assessment.isFirst(currSubmission));
next.setEnabled(currSubmission == null || !assessment.isLast(currSubmission));
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add(
createItem("Candidato:", exam.getCandidate().getEmail()),
createItem("Hora de inicio:", Optional.ofNullable(exam.getStartingTime())
createItem("Candidato:", assessment.getCandidate().getEmail()),
createItem("Hora de inicio:", Optional.ofNullable(assessment.getStartingTime())
.map(t -> ZonedDateTime.ofInstant(t,
ZoneId.of("GMT-4")).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME))
.orElse("N/A"))
@ -258,14 +259,14 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
this.exam = this.examService.getExam(UUID.fromString(s));
this.assessment = this.assessmentService.getAssessment(UUID.fromString(s));
if (this.exam == null) {
if (this.assessment == null) {
throw new NotFoundException();
}
this.currSubmission = this.exam.isStarted()
? this.examService.getNextSubmission(exam.getId(), null, false)
this.currSubmission = this.assessment.isStarted()
? this.assessmentService.getNextSubmission(assessment.getId(), null, false)
: null;
updateUI();

View File

@ -0,0 +1,17 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Timeoff")
@Route(value = "/timeoffs/me", layout = MainLayout.class)
public class TimeoffView extends Main {
}

View File

@ -0,0 +1,16 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
@SpringComponent
@Scope("prototype")
@PageTitle("Timesheets")
@Route(value = "/timesheets", layout = MainLayout.class)
@PermitAll
public class TimesheestReportView extends Main {
}

View File

@ -0,0 +1,17 @@
package com.primefactorsolutions.views;
import com.vaadin.flow.component.html.Main;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import jakarta.annotation.security.PermitAll;
import org.springframework.context.annotation.Scope;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Timesheet")
@Route(value = "/timesheets/me", layout = MainLayout.class)
public class TimesheetView extends Main {
}

View File

@ -1,135 +0,0 @@
package com.primefactorsolutions.views.admin;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.TimeOffService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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;
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.data.provider.ListDataProvider;
import com.vaadin.flow.function.ValueProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
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.grid.VGrid;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
import java.util.stream.IntStream;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Registro de Vacaciones")
@Route(value = "/time-off/list", layout = MainLayout.class)
public class TimeOffListView extends BaseView {
private final TimeOffService timeOffService;
private final VGrid<TimeOff> timeOffGrid = new VGrid<>();
private ComboBox<Integer> yearFilter;
public TimeOffListView(final AuthenticationContext authenticationContext,
final TimeOffService timeOffService) {
super(authenticationContext);
this.timeOffService = timeOffService;
initializeView();
}
private void refreshGridListHoursWorked(final Integer year) {
final List<TimeOff> timeOffs = timeOffService.findTimeOffs(year);
timeOffGrid.setDataProvider(new ListDataProvider<>(timeOffs));
timeOffGrid.getDataProvider().refreshAll();
}
private void initializeView() {
getCurrentPageLayout().add(createAddTimeOff());
setupFilters();
setupListHoursWorkedGrid();
getCurrentPageLayout().add(timeOffGrid);
refreshGridListHoursWorked(yearFilter.getValue());
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createYearFilter());
getCurrentPageLayout().add(hl);
}
private ComboBox<Integer> createYearFilter() {
yearFilter = new ComboBox<>("Year");
final int nowYear = LocalDate.now().getYear();
final List<Integer> years = IntStream.range(0, 2).mapToObj(y -> nowYear - y).toList();
yearFilter.setItems(years);
yearFilter.setValue(years.getFirst());
yearFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(event.getValue())
);
return yearFilter;
}
private void setupListHoursWorkedGrid() {
timeOffGrid.addColumn(e -> e.getCategory().name())
.setHeader("Categoria");
timeOffGrid.addColumn(e -> e.getType().name())
.setHeader("Tipo");
timeOffGrid.addColumn(TimeOff::getDate)
.setHeader("Fecha");
timeOffGrid.addColumn(TimeOff::getDuration).setHeader("Duracion");
timeOffGrid.addColumn(TimeOff::getExpiration).setHeader("Expiracion");
timeOffGrid.addComponentColumn((ValueProvider<TimeOff, Component>) timeOff -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.EYE, "Ver");
viewItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
navigateToTimeOffView(timeOff.getId(), "view");
});
final MenuItem editItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.PENCIL, "Editar");
editItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
navigateToTimeOffView(timeOff.getId(), "edit");
});
return menuBar;
});
}
private void navigateToTimeOffView(final UUID idRecord, final String action) {
getUI().ifPresent(ui -> ui.navigate(TimeOffView.class, idRecord.toString() + "/" + action));
}
private Button createButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
final Button button = new Button(label);
if (isPrimary) {
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
}
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddTimeOff() {
return createButton("Agregar Vacacion", this::navigateToTimeOff, true);
}
private void navigateToTimeOff() {
getUI().ifPresent(ui -> ui.navigate(TimeOffView.class, "new"));
}
}

View File

@ -1,109 +0,0 @@
package com.primefactorsolutions.views.admin;
import com.primefactorsolutions.model.TimeOff;
import com.primefactorsolutions.model.TimeOffRequestType;
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.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.components.datepicker.VDatePicker;
import java.time.LocalDate;
import java.time.YearMonth;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Vacaciones")
@Route(value = "/time-off/:hours-workedId?/:action?", layout = MainLayout.class)
public class TimeOffView extends BaseEntityForm<TimeOff> implements HasUrlParameter<String> {
private final ComboBox<TimeOffRequestType> category = new ComboBox<>("Categoria");
private final ComboBox<TimeOff.Type> type = new ComboBox<>("Tipo");
private final VDatePicker date = new VDatePicker("Fecha");
private final NumberField duration = new NumberField("Duracion");
private final NumberField expiration = new NumberField("Expiracion");
private final TimeOffService timeOffService;
public TimeOffView(final AuthenticationContext authenticationContext,
final TimeOffService timeOffService) {
super(authenticationContext, TimeOff.class);
this.timeOffService = timeOffService;
initializeDateField();
category.setItems(TimeOffRequestType.values());
type.setItems(TimeOff.Type.values());
this.setSavedHandler(this::saveTimeOff);
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String s = params.get("hours-workedId").orElse(null);
if ("new".equals(action) || s == null) {
setEntityWithEnabledSave(new TimeOff());
} else {
final UUID timeOffId = UUID.fromString(s);
final TimeOff timeOff = timeOffService.getTimeOff(timeOffId);
if ("edit".equals(action) && !s.isEmpty()) {
setEntityWithEnabledSave(timeOff);
} else if ("view".equals(action) && !s.isEmpty()) {
setEntity(timeOff);
duration.setReadOnly(true);
expiration.setReadOnly(true);
category.setReadOnly(true);
type.setReadOnly(true);
date.setReadOnly(true);
}
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(
category,
type,
date,
duration,
expiration
);
}
private void initializeDateField() {
final LocalDate today = LocalDate.now();
final YearMonth currentMonth = YearMonth.of(today.getYear(), today.getMonth());
final LocalDate startOfMonth = currentMonth.atDay(1);
date.setWidthFull();
date.setMin(startOfMonth);
date.setMax(today);
date.setValue(today);
}
private void saveTimeOff(final TimeOff timeOff) {
if (isFormValid()) {
timeOffService.saveTimeOff(timeOff);
closeForm();
}
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate(TimeOffListView.class));
}
private boolean isFormValid() {
return date.getValue() != null;
}
}

View File

@ -1,54 +0,0 @@
package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.service.CandidateService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.Map;
@SpringComponent
@Scope("prototype")
@PageTitle("Candidates")
@Route(value = "/candidates", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class CandidatesListView extends BaseView {
public CandidatesListView(final AuthenticationContext authenticationContext,
final CandidateService candidateService) {
super(authenticationContext);
final HorizontalLayout hl = new HorizontalLayout();
final Button addCandidate = new Button("Add Candidate");
addCandidate.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addCandidate.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().flatMap(ui -> ui.navigate(CandidateView.class, "new"));
});
hl.add(addCandidate);
final VGrid<Candidate> grid = new VGrid<>(Candidate.class);
grid.setColumns("email");
grid.setAllRowsVisible(true);
grid.addComponentColumn(candidate ->
MenuBarUtils.menuBar(Map.of(Pair.of("Edit", VaadinIcon.PENCIL), menuItemClickEvent ->
getUI().flatMap(ui -> ui.navigate(CandidateView.class, candidate.getId().toString())))));
grid.setDataProvider(new ListDataProvider<>(candidateService.getCandidates()));
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,88 +0,0 @@
package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.*;
import com.primefactorsolutions.service.CandidateService;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.EvaluationService;
import com.primefactorsolutions.views.BaseEntityForm;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.EntityComboBox;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.textfield.IntegerField;
import com.vaadin.flow.router.BeforeEvent;
import com.vaadin.flow.router.HasUrlParameter;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.fields.ElementCollectionField;
import org.vaadin.firitin.fields.EnumSelect;
import java.util.List;
import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Evaluations")
@Route(value = "/evaluations", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class EvaluationView extends BaseEntityForm<Evaluation> implements HasUrlParameter<String> {
private final CandidateService candidateService;
private final EvaluationService evaluationService;
private final EmployeeService employeeService;
private final EntityComboBox<Candidate> candidate = new EntityComboBox<>("Candidate");
private final EntityComboBox<Employee> interviewer = new EntityComboBox<>("Interviewer");
private final EnumSelect<EmployeePosition> candidatePosition =
new EnumSelect<>("Position", EmployeePosition.class);
private final IntegerField points = new IntegerField("Points");
private final Text skillLabel = new Text("Skills");
private final ElementCollectionField<SkillEvaluation> skillEvaluations =
new ElementCollectionField<>(SkillEvaluation.class);
public EvaluationView(final AuthenticationContext authenticationContext,
final CandidateService candidateService,
final EmployeeService employeeService,
final EvaluationService evaluationService) {
super(authenticationContext, Evaluation.class);
this.employeeService = employeeService;
this.evaluationService = evaluationService;
this.candidateService = candidateService;
this.candidate.setWidthFull();
this.interviewer.setWidthFull();
this.points.setWidthFull();
this.skillEvaluations.setWidthFull();
this.candidatePosition.setWidthFull();
this.candidate.setItems(this.candidateService.getCandidates());
this.interviewer.setItems(this.employeeService.getEmployees());
setSavedHandler(evaluation -> goTo(EvaluationsListView.class));
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var evaluation = evaluationService.getEvaluation(UUID.fromString(s));
setEntityWithEnabledSave(evaluation);
} else {
setEntityWithEnabledSave(new Evaluation());
}
}
@Override
protected List<Component> getFormComponents() {
return List.of(
candidate,
points,
candidatePosition,
skillLabel,
skillEvaluations,
interviewer
);
}
}

View File

@ -1,52 +0,0 @@
package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Evaluation;
import com.primefactorsolutions.service.EvaluationService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
import java.util.Map;
@SpringComponent
@Scope("prototype")
@PageTitle("Evaluations")
@Route(value = "/evaluations", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class EvaluationsListView extends BaseView {
public EvaluationsListView(final AuthenticationContext authenticationContext,
final EvaluationService evaluationService) {
super(authenticationContext);
final HorizontalLayout hl = new HorizontalLayout();
final Button addEvaluation = new Button("Add Evaluation");
addEvaluation.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addEvaluation.addClickListener(buttonClickEvent -> {
this.getUI().flatMap(ui -> ui.navigate(EvaluationView.class, "new"));
});
hl.add(addEvaluation);
final VGrid<Evaluation> grid = new VGrid<>(Evaluation.class);
grid.setColumns("candidate.email");
grid.setAllRowsVisible(true);
grid.addComponentColumn(evaluation ->
MenuBarUtils.menuBar(Map.of(Pair.of("Edit", VaadinIcon.PENCIL), menuItemClickEvent ->
getUI().flatMap(ui -> ui.navigate(EvaluationView.class, evaluation.getId().toString())))));
grid.setDataProvider(new ListDataProvider<>(evaluationService.getEvaluations()));
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,86 +0,0 @@
package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Exam;
import com.primefactorsolutions.service.ExamService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.SubmissionView;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.confirmdialog.ConfirmDialog;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import org.vaadin.addon.stefan.clipboard.ClientsideClipboard;
import org.vaadin.firitin.components.grid.VGrid;
@SpringComponent
@Scope("prototype")
@PageTitle("Exams")
@Route(value = "/exams", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class ExamsListView extends BaseView {
public ExamsListView(final AuthenticationContext authenticationContext,
final ExamService examService) {
super(authenticationContext);
final HorizontalLayout hl = new HorizontalLayout();
final Button addExam = new Button("Add Exam");
addExam.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addExam.addClickListener(buttonClickEvent ->
getUI().flatMap(ui -> ui.navigate(ExamView.class, "new")));
hl.add(addExam);
final VGrid<Exam> grid = new VGrid<>(Exam.class);
grid.setColumns("id", "candidate.email");
final Grid.Column<Exam> statusColumn = grid.addColumn(exam ->
exam.getExamEvents().isEmpty()
? "N/A"
: exam.getExamEvents().getLast().getStatus().name());
statusColumn.setHeader("Status");
grid.addComponentColumn(exam -> MenuBarUtils.menuBar(
Pair.of("View", __ ->
getUI().flatMap(ui -> ui.navigate(SubmissionView.class, exam.getId().toString()))),
Pair.of("Copy", __ ->
ClientsideClipboard.writeToClipboard(
String.format("email: %s link: "
+ "https://intra.primefactorsolutions.com/candidate-exam/%s",
exam.getCandidate().getEmail(),
exam.getId()))),
Pair.of("Email", __ -> {
ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("Send Link Email");
dialog.setText(String.format("Enviar link por email al candidato %s?",
exam.getCandidate().getEmail()));
dialog.setCancelable(true);
dialog.setConfirmText("Enviar");
dialog.setConfirmButtonTheme("primary");
dialog.addConfirmListener(confirmEvent -> {
try {
examService.sendEmail(exam);
} catch (Exception e) {
Notification.show("Error sending email: " + e.getMessage(), 10_000,
Notification.Position.TOP_CENTER);
}
});
dialog.open();
})
));
grid.setDataProvider(new ListDataProvider<>(examService.getExams()));
grid.setAllRowsVisible(true);
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,50 +0,0 @@
package com.primefactorsolutions.views.assessment;
import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.QuestionService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.data.provider.ListDataProvider;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.context.annotation.Scope;
import org.vaadin.firitin.components.grid.VGrid;
@SpringComponent
@Scope("prototype")
@PageTitle("Questions")
@Route(value = "/questions", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
public class QuestionsListView extends BaseView {
public QuestionsListView(final AuthenticationContext authenticationContext, final QuestionService questionService) {
super(authenticationContext);
final HorizontalLayout hl = new HorizontalLayout();
final Button addQuestion = new Button("Add Question");
addQuestion.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
addQuestion.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().flatMap(ui -> ui.navigate(QuestionView.class, "new")));
hl.add(addQuestion);
final VGrid<Question> grid = new VGrid<>(Question.class);
grid.setColumns("title", "description", "timeMinutes");
grid.addComponentColumn(question ->
MenuBarUtils.menuBar(Pair.of("Edit", __ ->
getUI().flatMap(ui -> ui.navigate(QuestionView.class, question.getId().toString())))));
grid.setDataProvider(new ListDataProvider<>(questionService.getQuestions()));
grid.setAllRowsVisible(true);
getCurrentPageLayout().add(hl, grid);
}
}

View File

@ -1,60 +0,0 @@
package com.primefactorsolutions.views.employee;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.ReportService;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.*;
import com.vaadin.flow.server.StreamResource;
import jakarta.annotation.security.PermitAll;
import java.io.ByteArrayInputStream;
import java.util.UUID;
@PermitAll
@PageTitle("Reporte excel")
@Route("employee-report")
public class EmployeeReportView extends VerticalLayout implements HasUrlParameter<String> {
private final EmployeeService employeeService;
private final ReportService reportService;
public EmployeeReportView(final EmployeeService employeeService, final ReportService reportService) {
this.employeeService = employeeService;
this.reportService = reportService;
Button backButton = new Button("Volver al Reporte de Empleados", event ->
UI.getCurrent().navigate(EmployeesListView.class));
backButton.addClassName("back-button");
add(backButton);
}
@Override
public void setParameter(final BeforeEvent event, @OptionalParameter final String employeeId) {
if (employeeId != null) {
UUID id = UUID.fromString(employeeId);
Employee employee = employeeService.getEmployee(id);
if (employee != null) {
generateExcelReport(employee);
} else {
Notification.show("Empleado no encontrado", 3000, Notification.Position.MIDDLE);
}
}
}
private void generateExcelReport(final Employee employee) {
try {
byte[] excelContent = reportService.generateExcelReport(employee);
StreamResource resource = new StreamResource(
employee.getFirstName() + "_" + employee.getLastName() + "_report.xlsx",
() -> new ByteArrayInputStream(excelContent)
);
Anchor downloadLink = new Anchor(resource, "Descargar Reporte Excel");
downloadLink.getElement().setAttribute("download", true);
add(downloadLink);
} catch (Exception e) {
Notification.show("Error al generar el reporte Excel", 3000, Notification.Position.MIDDLE);
}
}
}

View File

@ -1,206 +0,0 @@
package com.primefactorsolutions.views.employee;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.Constants;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
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;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
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 org.springframework.context.annotation.Scope;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
@SpringComponent
@Scope("prototype")
@PageTitle("Employees")
@Route(value = "/employees", layout = MainLayout.class)
@RolesAllowed("ROLE_ADMIN")
@Slf4j
public class EmployeesListView extends BaseView {
private final EmployeeService employeeService;
private final PagingGrid<Employee> employeePagingGrid = new PagingGrid<>(Employee.class);
public EmployeesListView(final AuthenticationContext authenticationContext, final EmployeeService employeeService) {
super(authenticationContext);
this.employeeService = employeeService;
setupView();
refreshGrid();
}
private void setupView() {
configureTable();
final HorizontalLayout hl = new HorizontalLayout(createAddEmployeeButton(), createExportButton());
getCurrentPageLayout().add(hl);
getCurrentPageLayout().add(employeePagingGrid);
}
private Button createExportButton() {
final StreamResource excelResource = new StreamResource("employees.xlsx", this::generateExcel);
final 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() {
final List<Employee> 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() : "");
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() : "");
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.getPhoneNumberProfessional() != null ? employee
.getPhoneNumberProfessional() : "");
row.createCell(17).setCellValue(employee.getProfessionalEmail() != null ? employee
.getProfessionalEmail() : "");
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().toString()
: "");
}
workbook.write(out);
return new ByteArrayInputStream(out.toByteArray());
} catch (IOException e) {
log.error("Error generating excel", e);
return null;
}
}
private void configureTable() {
employeePagingGrid.setColumns("username", "firstName", "lastName", "status");
employeePagingGrid.addComponentColumn(employee -> MenuBarUtils.menuBar(
Pair.of("View", __ -> navigateToEmployeeView(employee)),
Pair.of("Edit", __ -> navigateToEditView(employee))));
setupPagingGrid();
}
private Button createButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
final 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, true);
}
private void navigateToEditView(final Employee employee) {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, employee.getId().toString() + "/edit"));
}
private void navigateToEmployeeView(final Employee employee) {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, employee.getId().toString() + "/view"));
}
private void navigateToAddEmployeeView() {
getUI().ifPresent(ui -> ui.navigate(EmployeeView.class, "new"));
}
private void setupPagingGrid() {
employeePagingGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
employeePagingGrid.setPageSize(Constants.PAGE_SIZE);
}
private void refreshGrid() {
employeePagingGrid.setPagingDataProvider((page, pageSize) -> fetchEmployees((int) page, pageSize));
}
private List<Employee> fetchEmployees(final int page, final int pageSize) {
int start = page * pageSize;
if (hasSortOrder()) {
return fetchSortedEmployees(start, pageSize);
}
return employeeService.findEmployees(start, pageSize);
}
private boolean hasSortOrder() {
return !employeePagingGrid.getSortOrder().isEmpty();
}
private List<Employee> fetchSortedEmployees(final int start, final int pageSize) {
final GridSortOrder<Employee> sortOrder = employeePagingGrid.getSortOrder().getFirst();
return employeeService.findEmployees(start, pageSize,
sortOrder.getSorted().getKey(),
sortOrder.getDirection() == SortDirection.ASCENDING);
}
}

View File

@ -1,525 +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.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.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 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;
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 BaseEntityForm<TimeOffRequest> implements HasUrlParameter<String> {
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 TimeOffService timeOffService;
private final TimeOffRequestService timeOffRequestService;
private final EmployeeService employeeService;
private Employee employee;
public TimeOffRequestView(final AuthenticationContext authenticationContext,
final TimeOffRequestService timeOffRequestService,
final TimeOffService timeOffService,
final EmployeeService employeeService) {
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);
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(
category,
state,
expiration,
startDate,
endDate,
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.setReadOnly(false);
endDate.setReadOnly(false);
} else {
startDate.setReadOnly(false);
endDate.setReadOnly(true);
}
}
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);
daysBalance.setReadOnly(option);
daysToBeTake.setReadOnly(option);
}
private void saveRequest(final TimeOffRequest 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 prepareRequest(final TimeOffRequest request) {
request.setStartDate(startDate.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(TimeOffRequestsListView.class));
}
private boolean isFormValid() {
return !state.isEmpty()
&& expiration.getValue() != null
&& startDate.getValue() != null
&& endDate.getValue() != null
&& daysBalance.getValue() != null
&& daysToBeTake.getValue() != null;
}
}

View File

@ -1,343 +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.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
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.button.ButtonVariant;
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.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
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.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Time-Off Requests")
@Route(value = "/time-off/requests", layout = MainLayout.class)
@PermitAll
public class TimeOffRequestsListView extends BaseView {
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimeOffRequest> requestsGrid = new PagingGrid<>();
private final ComboBox<Employee> employeeFilter = new ComboBox<>("Empleado");
private final ComboBox<Team> teamFilter = new ComboBox<>("Equipo");
private final ComboBox<TimeOffRequestType> categoryFilter = new ComboBox<>("Categoría");
private final ComboBox<TimeOffRequestStatus> stateFilter = new ComboBox<>("Estado");
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();
refreshGeneralRequestsGrid(employeeFilter.getValue(), teamFilter.getValue(), categoryFilter.getValue(),
stateFilter.getValue());
}
private void initializeView() {
final Button newRequestButton = new Button("Crear nueva peticion", event -> navigateToAddNew());
newRequestButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
final HorizontalLayout hl = new HorizontalLayout(newRequestButton);
if (isRoleAdmin()) {
final Button downloadReportButton = new Button("Descargar reporte de rechazos", event -> downloadReport());
hl.add(downloadReportButton);
}
getCurrentPageLayout().add(hl);
setupFilters();
setupRequestsGrid();
}
private void navigateToAddNew() {
getUI().ifPresent(ui -> ui.navigate(TimeOffRequestView.class, "new"));
}
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 setupRequestsGrid() {
requestsGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
requestsGrid.addColumn(this::getTeamName).setHeader("Equipo");
requestsGrid.addColumn(this::getCategory).setHeader("Categoría");
requestsGrid.addColumn(this::getDates).setHeader("Dias");
requestsGrid.addColumn(this::getState).setHeader("Estado");
requestsGrid.addColumn(this::getUpdated).setHeader("Fecha Actualizacion");
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");
approveItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
actionForRequest(timeOffRequest.getId(), TimeOffRequestStatus.APROBADO));
final MenuItem rejectItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.BAN, "Rechazar");
rejectItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent ->
actionForRequest(timeOffRequest.getId(), TimeOffRequestStatus.RECHAZADO));
return menuBar;
});
requestsGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestsGrid.setPageSize(PAGE_SIZE);
getCurrentPageLayout().add(requestsGrid);
}
private void actionForRequest(final UUID selectedRequestId, final TimeOffRequestStatus status) {
TimeOffRequest request = requestService.findTimeOffRequest(selectedRequestId);
request.setState(status);
requestService.saveTimeOffRequest(request);
refreshGeneralRequestsGrid(null, null, null, null);
}
private void refreshGeneralRequestsGrid(final Employee employee,
final Team team,
final TimeOffRequestType category,
final TimeOffRequestStatus timeOffRequestStatus) {
requestsGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestsGrid.getPageSize());
return fetchFilteredRequests(start, pageSize, employee, team, category, timeOffRequestStatus);
});
requestsGrid.getDataProvider().refreshAll();
}
private List<TimeOffRequest> fetchFilteredRequests(final int start,
final int pageSize,
final Employee employee,
final Team team,
final TimeOffRequestType category,
final TimeOffRequestStatus timeOffRequestStatus) {
List<TimeOffRequest> filteredRequests = requestService.findAllTimeOffRequests();
if (employee != null) {
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null) {
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getEmployee().getTeam() != null
&& emp.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
if (category != null) {
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getCategory().equals(category))
.collect(Collectors.toList());
}
if (timeOffRequestStatus != null) {
filteredRequests = filteredRequests.stream()
.filter(emp -> emp.getState().equals(timeOffRequestStatus))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredRequests.size());
return filteredRequests.subList(start, end);
}
private String getEmployeeFullName(final TimeOffRequest request) {
Employee employee = request.getEmployee();
return getEmployeeFullNameLabel(employee);
}
private String getEmployeeFullNameLabel(final Employee employee) {
return employee.getFirstName() + " " + employee.getLastName();
}
private String getTeamName(final TimeOffRequest request) {
Team team = request.getEmployee().getTeam();
return team != null ? team.getName() : "Sin asignar";
}
private String getTeamLabel(final Team team) {
return team.getName();
}
private String getCategory(final TimeOffRequest request) {
return String.valueOf(request.getCategory());
}
private String getDates(final TimeOffRequest request) {
return String.format("de %s a %s", request.getStartDate(), request.getEndDate());
}
private String getState(final TimeOffRequest request) {
return request.getState().name();
}
private String getUpdated(final TimeOffRequest request) {
return DateTimeFormatter.ofPattern("yyyy/dd/MM hh:mm")
.format(request.getUpdated().atOffset(ZoneOffset.ofHours(-5)));
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter.setClearButtonVisible(true);
employeeFilter.setPlaceholder("Seleccionar ...");
if (isRoleAdmin()) {
final List<Employee> employees = employeeService.findAllEmployees();
employeeFilter.setItems(employees);
} else {
final Employee employee = employeeService.getEmployee(getEmployeeId().get());
employeeFilter.setItems(List.of(employee));
employeeFilter.setValue(employee);
employeeFilter.setReadOnly(true);
}
employeeFilter.setItemLabelGenerator(this::getEmployeeFullNameLabel);
employeeFilter.addValueChangeListener(event ->
refreshGeneralRequestsGrid(
event.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter.setClearButtonVisible(true);
teamFilter.setPlaceholder("Seleccionar ...");
final List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.addValueChangeListener(event ->
refreshGeneralRequestsGrid(
employeeFilter.getValue(),
event.getValue(),
categoryFilter.getValue(),
stateFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<TimeOffRequestType> createCategoryFilter() {
categoryFilter.setPlaceholder("Seleccionar ...");
categoryFilter.setClearButtonVisible(true);
categoryFilter.setItems(TimeOffRequestType.values());
categoryFilter.addValueChangeListener(event ->
refreshGeneralRequestsGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue(),
stateFilter.getValue()
)
);
return categoryFilter;
}
private ComboBox<TimeOffRequestStatus> createStateFilter() {
stateFilter.setPlaceholder("Seleccionar ...");
stateFilter.setClearButtonVisible(true);
stateFilter.setItems(TimeOffRequestStatus.values());
stateFilter.addValueChangeListener(event ->
refreshGeneralRequestsGrid(
employeeFilter.getValue(),
teamFilter.getValue(),
categoryFilter.getValue(),
event.getValue()
)
);
return stateFilter;
}
private void downloadReport() {
StreamResource resource = generateGeneralVacationReport();
getUI().ifPresent(ui -> openDocumentStream(resource, ui));
}
private StreamResource generateGeneralVacationReport() {
List<TimeOffRequest> requests = requestService.findAllTimeOffRequests().stream()
.filter(request -> request.getState() == TimeOffRequestStatus.RECHAZADO)
.collect(Collectors.toList());
ByteArrayInputStream excelStream = generateExcelReport(requests);
return new StreamResource("reporte_de_solicitudes_rechazadas_" + LocalDate.now() + ".xlsx",
() -> excelStream);
}
private void openDocumentStream(final StreamResource resource, final UI ui) {
StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource);
ui.getPage().open(registration.getResourceUri().toString());
}
private ByteArrayInputStream generateExcelReport(final List<TimeOffRequest> requests) {
try (Workbook workbook = new XSSFWorkbook()) {
Sheet sheet = workbook.createSheet("REPORTE_DE_SOLICITUDES_DE_VACACIONES_RECHAZADAS");
Row headerRow = sheet.createRow(0);
String[] headers = {"Empleado", "Categoria", "Observaciones"};
for (int i = 0; i < headers.length; i++) {
Cell cell = headerRow.createCell(i);
cell.setCellValue(headers[i]);
}
int rowIndex = 1;
for (TimeOffRequest request : requests) {
Row row = sheet.createRow(rowIndex++);
row.createCell(0).setCellValue(getEmployeeFullName(request));
row.createCell(1).setCellValue(getCategory(request));
}
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);
}
}
}

View File

@ -1,809 +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.primefactorsolutions.views.util.MenuBarUtils;
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.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamRegistration;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.spring.annotation.SpringComponent;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.PermitAll;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.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.time.Year;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@Scope("prototype")
@PageTitle("Requests")
@Route(value = "/time-off/summary", layout = MainLayout.class)
@PermitAll
public class TimeOffSummaryListView extends BaseView {
private static final Map<UUID, TimeOffSummary> SUMMARY_MAP = new ConcurrentHashMap<>();
private final TimeOffRequestService requestService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final TimeOffService timeOffService;
private final PagingGrid<Employee> requestGrid = new PagingGrid<>();
private final ComboBox<Employee> employeeFilter = new ComboBox<>("Empleado");;
private final ComboBox<Team> teamFilter = new ComboBox<>("Equipo");
public TimeOffSummaryListView(
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(employeeFilter.getValue(), teamFilter.getValue());
}
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());
getCurrentPageLayout().add(hl);
}
private void setupRequestGrid() {
requestGrid.addColumn(this::getEmployeeFullName).setHeader("Empleado");
requestGrid.addColumn(this::getTeamName).setHeader("Equipo");
requestGrid.addColumn(this::getRemainingHolidays).setHeader("Remaining holiday");
requestGrid.addColumn(this::getRemainingPersonal).setHeader("Remaining personal");
requestGrid.addColumn(this::getRemainingVacation).setHeader("Remaining vacation");
requestGrid.addColumn(this::getRemainingTotal).setHeader("Remaining total");
if (isRoleAdmin()) {
requestGrid.addComponentColumn(employee -> MenuBarUtils.menuBar(
Pair.of("Download", __ -> downloadEmployeeReport(employee.getId()))));
}
requestGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
requestGrid.setPageSize(PAGE_SIZE);
}
private Double getRemainingHolidays(final Employee employee) {
final TimeOffSummary summary = SUMMARY_MAP.computeIfAbsent(employee.getId(), __ -> getTimeOffSummary(employee));
return summary.remainingHolidayDays;
}
private Double getRemainingPersonal(final Employee employee) {
final TimeOffSummary summary = SUMMARY_MAP.computeIfAbsent(employee.getId(), __ -> getTimeOffSummary(employee));
return summary.remainingPersonalDays;
}
private Double getRemainingVacation(final Employee employee) {
final TimeOffSummary summary = SUMMARY_MAP.computeIfAbsent(employee.getId(), __ -> getTimeOffSummary(employee));
return summary.remainingVacationDays;
}
private Double getRemainingTotal(final Employee employee) {
final TimeOffSummary summary = SUMMARY_MAP.computeIfAbsent(employee.getId(), __ -> getTimeOffSummary(employee));
return summary.getTotalRemaining();
}
private void refreshGeneralRequestGrid(final Employee employee,
final Team team) {
requestGrid.setPagingDataProvider((page, pageSize) -> {
int start = (int) (page * requestGrid.getPageSize());
return fetchFilteredEmployees(start, pageSize, employee, team);
});
requestGrid.getDataProvider().refreshAll();
}
private List<Employee> fetchFilteredEmployees(final int start,
final int pageSize,
final Employee employee,
final Team team) {
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());
}
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 getGeneralTotal(final Employee employee) {
final List<TimeOffRequest> employeeRequests = requestService.findRequestsByEmployeeId(employee.getId());
final List<TimeOff> timeOffs = timeOffService.findVacations();
final List<Double> vacationDays = calculateVacationDays(employee);
double utilizedVacationCurrentDays = vacationDays.get(1);
final 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);
final List<TimeOffRequest> vacationPreviousRequests = requestService
.findByEmployeeAndCategory(employee.getId(), TimeOffRequestType.VACACION_GESTION_ANTERIOR);
if (vacationPreviousRequests != null && !vacationPreviousRequests.isEmpty()) {
utilizedVacationPreviousDays = vacationPreviousRequests.getLast().getDaysBalance();
}
final double totalVacationPreviousDays = vacationDays.getFirst()
- (vacationDays.getFirst() - utilizedVacationPreviousDays);
final double totalUtilized = calculateTotalUtilized(employeeRequests);
final double totalVacations = totalVacationCurrentDays + totalVacationPreviousDays;
final 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.setPlaceholder("Seleccionar ...");
employeeFilter.setClearButtonVisible(true);
if (isRoleAdmin()) {
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employeeFilter.setItems(employees);
} else {
final Employee employee = employeeService.getEmployee(getEmployeeId().get());
employeeFilter.setItems(List.of(employee));
employeeFilter.setValue(employee);
employeeFilter.setReadOnly(true);
}
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.addValueChangeListener(event ->
refreshGeneralRequestGrid(
event.getValue(),
teamFilter.getValue()
)
);
return employeeFilter;
}
private ComboBox<Team> createTeamFilter() {
teamFilter.setPlaceholder("Seleccionar ...");
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()
)
);
return teamFilter;
}
private ByteArrayInputStream generateExcelReport(final List<Employee> employees) {
try (Workbook workbook = new XSSFWorkbook()) {
final Sheet sheet = workbook.createSheet("REPORTE_GENERAL_DE_VACACIONES");
final 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 (final 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() {
final List<Employee> employees = employeeService.findAllEmployees();
final ByteArrayInputStream excelStream = generateExcelReport(employees);
return new StreamResource("reporte_general_de_vacaciones_" + LocalDate.now() + ".xlsx",
() -> excelStream);
}
private void downloadReport() {
final StreamResource resource = generateGeneralVacationReport();
getUI().ifPresent(ui -> openDocumentStream(resource, ui));
}
private void openDocumentStream(final StreamResource resource, final UI ui) {
final StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource);
ui.getPage().open(registration.getResourceUri().toString());
}
private TimeOffSummary getTimeOffSummary(final Employee employee) {
final boolean isMale = employee.getGender() == Employee.Gender.MALE;
final int currentYear = LocalDate.now().getYear();
final LocalDate currentDate = LocalDate.now();
final List<TimeOff> timeOffs = timeOffService.findVacations();
double healthLicence = getHealthLicence(employee.getId());
double totalFixedAndMovableHolidays = calculateHolidayDays(timeOffs);
double totalPersonalDays = calculatePersonalDays(timeOffs, isMale);
final List<Double> vacationDays = calculateVacationDays(employee);
double totalVacationCurrentDays = calculateUtilizedVacationDays(
employee.getId(),
vacationDays.get(1),
TimeOffRequestType.VACACION_GESTION_ACTUAL
);
double totalVacationPreviousDays = calculateUtilizedVacationDays(
employee.getId(),
vacationDays.get(0),
TimeOffRequestType.VACACION_GESTION_ANTERIOR
);
final List<TimeOffRequest> requests = requestService.findRequestsByEmployeeId(employee.getId());
double utilizedFixedAndMovableHolidays = calculateHolidayUtilizedDays(requests, currentYear);
double utilizedPersonalDays = calculatePersonalDaysUtilized(requests, isMale, currentYear);
double remainingHolidayDays = calculateRemainingHolidayDays(
totalFixedAndMovableHolidays,
utilizedFixedAndMovableHolidays,
employee.getDateOfExit(),
currentDate
);
double remainingPersonalDays = calculateRemainingPersonalDays(
totalPersonalDays,
utilizedPersonalDays,
healthLicence,
employee.getDateOfExit(),
currentDate
);
double remainingVacationDays = calculateRemainingVacationDays(
totalVacationCurrentDays,
totalVacationPreviousDays,
employee.getDateOfExit(),
currentDate
);
return new TimeOffSummary(remainingHolidayDays, remainingPersonalDays, remainingVacationDays);
}
private record TimeOffSummary(double remainingHolidayDays, double remainingPersonalDays,
double remainingVacationDays) {
public double getTotalRemaining() {
return remainingHolidayDays + remainingPersonalDays + remainingVacationDays;
}
}
private double getHealthLicence(final UUID employeeId) {
List<TimeOffRequest> healthRequests = requestService
.findByEmployeeAndCategory(employeeId, TimeOffRequestType.PERMISOS_DE_SALUD);
return healthRequests != null && !healthRequests.isEmpty() ? healthRequests.getLast().getDaysBalance() : 2;
}
private double calculateUtilizedVacationDays(final UUID employeeId, final double vacationDays,
final TimeOffRequestType requestType) {
List<TimeOffRequest> vacationRequests = requestService.findByEmployeeAndCategory(employeeId, requestType);
if (vacationRequests != null && !vacationRequests.isEmpty()) {
return vacationRequests.getLast().getDaysBalance();
}
return vacationDays;
}
private double calculateRemainingVacationDays(final double totalVacationCurrentDays,
final double totalVacationPreviousDays,
final LocalDate exitDate,
final LocalDate currentDate) {
if (exitDate == null || exitDate.isAfter(currentDate)) {
return totalVacationCurrentDays + totalVacationPreviousDays;
}
return 0;
}
private double calculateRemainingHolidayDays(final double totalFixedAndMovableHolidays,
final double utilizedFixedAndMovableHolidays,
final LocalDate exitDate,
final LocalDate currentDate) {
if (exitDate == null || exitDate.isAfter(currentDate)) {
return totalFixedAndMovableHolidays - utilizedFixedAndMovableHolidays;
}
return 0;
}
private double calculateRemainingPersonalDays(final double totalPersonalDays,
final double utilizedPersonalDays,
final double healthLicence,
final LocalDate exitDate,
final LocalDate currentDate) {
if (exitDate == null || exitDate.isAfter(currentDate)) {
return (totalPersonalDays - utilizedPersonalDays) + healthLicence;
}
return 0;
}
private double calculateHolidayDays(final List<TimeOff> timeOffs) {
return timeOffs.stream()
.filter(req -> req.getType() != TimeOff.Type.OTHER)
.mapToDouble(TimeOff::getDuration)
.sum();
}
private double calculatePersonalDays(final List<TimeOff> timeOffs, final boolean isMale) {
return timeOffs.stream()
.filter(req -> req.getType() == TimeOff.Type.OTHER)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.mapToDouble(TimeOff::getDuration)
.sum();
}
private Set<TimeOffRequestType> getStandardExclusions() {
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> getMaleSpecificExclusions() {
return Set.of(
TimeOffRequestType.DIA_DE_LA_MUJER_INTERNACIONAL,
TimeOffRequestType.DIA_DE_LA_MUJER_NACIONAL
);
}
private double calculateHolidayUtilizedDays(final List<TimeOffRequest> requests, final int year) {
return requests.stream()
.filter(this::verificationIsHoliday)
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO
|| req.getState() == TimeOffRequestStatus.APROBADO)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private double calculatePersonalDaysUtilized(final List<TimeOffRequest> requests, final boolean isMale,
final int year) {
return requests.stream()
.filter(req -> !verificationIsHoliday(req))
.filter(req -> req.getState() == TimeOffRequestStatus.TOMADO
|| req.getState() == TimeOffRequestStatus.APROBADO)
.filter(req -> !getStandardExclusions().contains(req.getCategory()))
.filter(req -> !(isMale && getMaleSpecificExclusions().contains(req.getCategory())))
.filter(req -> !req.getCategory().name().startsWith("VACACION"))
.filter(req -> req.getCategory() != TimeOffRequestType.PERMISOS_DE_SALUD)
.filter(req -> getStartDateYear(req) == year)
.mapToDouble(TimeOffRequest::getDaysToBeTake)
.sum();
}
private int getStartDateYear(final TimeOffRequest request) {
if (request.getStartDate() != null) {
return request.getStartDate().getYear();
}
return 0;
}
private Boolean verificationIsHoliday(final TimeOffRequest request) {
TimeOff timeoff = timeOffService.findVacationByCategory(request.getCategory());
return timeoff.getType() != TimeOff.Type.OTHER;
}
private String getDateString(final LocalDate date) {
return (date != null) ? date.toString() : "";
}
private ByteArrayInputStream generatePdfReport(final UUID employeeId) {
final List<TimeOffRequest> requests = requestService.findRequestsByEmployeeId(employeeId);
final Employee employee = employeeService.getEmployee(employeeId);
final TimeOffSummary result = getTimeOffSummary(employee);
try (PDDocument document = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
PDPage page = new PDPage();
document.addPage(page);
List<TimeOffRequest> filteredRequests = requests.stream()
.filter(request ->
(request.getStartDate() == null || Year.from(request.getStartDate()).equals(Year.now()))
|| request.getCategory() == TimeOffRequestType.VACACION_GESTION_ACTUAL
|| request.getCategory() == TimeOffRequestType.VACACION_GESTION_ANTERIOR)
.toList();
PDPageContentStream contentStream = null;
try {
contentStream = new PDPageContentStream(document, page);
contentStream.setFont(PDType1Font.TIMES_BOLD, 18);
contentStream.beginText();
contentStream.newLineAtOffset(200, 750);
contentStream.showText("Reporte de Vacaciones");
contentStream.endText();
contentStream.setFont(PDType1Font.TIMES_ROMAN, 14);
contentStream.beginText();
contentStream.newLineAtOffset(50, 700);
contentStream.showText("Empleado: " + employee.getFirstName() + " " + employee.getLastName());
contentStream.newLineAtOffset(0, -15);
contentStream.showText("Equipo: " + employee.getTeam().getName());
contentStream.endText();
float tableTopY = 650;
float margin = 50;
float cellHeight = 20;
String[] headers = {"Categoría", "Estado", "Fecha de Inicio", "Fecha de Fin", "Días a Tomar"};
int columns = headers.length;
float[] columnWidths = new float[columns];
for (int i = 0; i < columns; i++) {
columnWidths[i] = getMaxColumnWidth(headers[i], requests, i);
}
contentStream.setFont(PDType1Font.TIMES_BOLD, 10);
float currentX = margin;
for (int i = 0; i < columns; i++) {
contentStream.addRect(currentX, tableTopY, columnWidths[i], -cellHeight);
contentStream.beginText();
contentStream.newLineAtOffset(currentX + 5, tableTopY - 15);
contentStream.showText(headers[i]);
contentStream.endText();
currentX += columnWidths[i];
}
contentStream.stroke();
contentStream.setFont(PDType1Font.TIMES_ROMAN, 10);
float currentY = tableTopY - cellHeight;
for (TimeOffRequest request : filteredRequests) {
String startDate = getDateString(request.getStartDate());
String endDate = getDateString(request.getEndDate());
String[] rowData = {
request.getCategory().name(),
request.getState().name(),
startDate,
endDate,
String.valueOf(request.getDaysToBeTake() != null ? request.getDaysToBeTake() : 0)
};
currentX = margin;
for (int i = 0; i < columns; i++) {
contentStream.addRect(currentX, currentY, columnWidths[i], -cellHeight);
contentStream.beginText();
contentStream.newLineAtOffset(currentX + 5, currentY - 15);
contentStream.showText(rowData[i]);
contentStream.endText();
currentX += columnWidths[i];
}
contentStream.stroke();
currentY -= cellHeight;
if (currentY < 50) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
currentY = 750;
}
}
currentY -= 30;
if (currentY < 80) {
contentStream.close();
page = new PDPage();
document.addPage(page);
contentStream = new PDPageContentStream(document, page);
currentY = 750;
}
contentStream.setFont(PDType1Font.TIMES_ROMAN, 14);
contentStream.beginText();
contentStream.newLineAtOffset(50, currentY - 20);
contentStream.showText("Total feriados fijos y movibles: " + result.remainingHolidayDays);
contentStream.newLineAtOffset(0, -20);
contentStream.showText("Total días libres personales: " + result.remainingPersonalDays);
contentStream.newLineAtOffset(0, -20);
contentStream.showText("Total vacaciones pendientes de uso: " + result.remainingVacationDays);
contentStream.newLineAtOffset(0, -20);
contentStream.showText("TOTAL GENERAL DE DÍAS DISPONIBLES: "
+ (result.remainingHolidayDays + result.remainingPersonalDays + result.remainingVacationDays));
contentStream.endText();
} finally {
if (contentStream != null) {
contentStream.close();
}
}
document.save(out);
return new ByteArrayInputStream(out.toByteArray());
} catch (IOException e) {
throw new UncheckedIOException("Error al generar el reporte", e);
}
}
private float getMaxColumnWidth(final String header, final List<TimeOffRequest> requests, final int columnIndex) {
float maxWidth = header.length();
for (TimeOffRequest request : requests) {
String value = switch (columnIndex) {
case 0 -> request.getCategory().name();
case 1 -> request.getState().name();
case 2 -> getDateString(request.getStartDate());
case 3 -> getDateString(request.getEndDate());
case 4 -> String.valueOf(request.getDaysToBeTake());
default -> "";
};
if (value != null) {
maxWidth = Math.max(maxWidth, value.length());
}
}
return maxWidth * 7;
}
private StreamResource generateVacationReport(final UUID employeeId) {
Employee employee = employeeService.getEmployee(employeeId);
String fileName = String.format("%s_%s-reporte_de_vacaciones_%s.pdf",
employee.getFirstName(),
employee.getLastName(),
LocalDate.now());
ByteArrayInputStream pdfStream = generatePdfReport(employeeId);
return new StreamResource(fileName, () -> pdfStream)
.setContentType("application/pdf")
.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
}
private void downloadEmployeeReport(final UUID employeeId) {
StreamResource resource = generateVacationReport(employeeId);
getUI().ifPresent(ui -> openDocumentEmployeeStream(resource, ui));
}
private void openDocumentEmployeeStream(final StreamResource resource, final UI ui) {
StreamRegistration registration = ui.getSession().getResourceRegistry().registerResource(resource);
ui.getPage().open(registration.getResourceUri().toString());
}
}

View File

@ -1,184 +0,0 @@
package com.primefactorsolutions.views.timesheet;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.TimesheetEntry;
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;
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 java.time.LocalDate;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Horas Trabajadas")
@Route(value = "/timesheet/:hours-workedId?/:action?", layout = MainLayout.class)
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");
private final TextField details = new TextField("Detalle");
private final VDatePicker date = new VDatePicker("Fecha");
private final NumberField hours = new NumberField("Horas");
private final TimesheetService timesheetService;
private final EmployeeService employeeService;
private final TeamService teamService;
private TimesheetEntry timesheetEntry;
public TimesheetEntryView(final AuthenticationContext authenticationContext,
final TimesheetService timesheetService,
final EmployeeService employeeService,
final TeamService teamService) {
super(authenticationContext, TimesheetEntry.class);
this.timesheetService = timesheetService;
this.employeeService = employeeService;
this.teamService = teamService;
task.setRequired(true);
initializeDateField();
initializeTeamField();
initializeEmployeeField();
configureTasks();
this.setSavedHandler(this::saveHoursWorked);
}
@Override
public void setParameter(final BeforeEvent beforeEvent, final String action) {
final RouteParameters params = beforeEvent.getRouteParameters();
final String s = params.get("hours-workedId").orElse(null);
if ("new".equals(action) || s == null) {
setEntityWithEnabledSave(new TimesheetEntry());
} else {
final UUID hoursWorkedId = UUID.fromString(s);
timesheetEntry = timesheetService.getTimesheetEntry(hoursWorkedId);
if ("edit".equals(action) && !s.isEmpty()) {
setEntityWithEnabledSave(timesheetEntry);
} else if ("view".equals(action) && !s.isEmpty()) {
setEntity(timesheetEntry);
setFormReadOnly();
}
}
}
private void setFormReadOnly() {
employee.setReadOnly(true);
team.setReadOnly(true);
task.setReadOnly(true);
details.setReadOnly(true);
date.setReadOnly(true);
hours.setReadOnly(true);
}
@Override
protected List<Component> getFormComponents() {
return List.of(
date,
team,
employee,
task,
details,
hours
);
}
private void configureTasks() {
task.setWidthFull();
task.setItems(
"Entrevistas",
"Reuniones",
"Colaboraciones",
"Aprendizajes",
"Proyectos PFS",
"Consulta Medica",
"Afiliación al Seguro",
"Fallas Tecnicas",
"Otros");
task.setPlaceholder("Selecciona una tarea...");
}
private void initializeTeamField() {
team.setWidthFull();
team.setItems(teamService.findAllTeams());
team.setItemLabelGenerator(Team::getName);
team.addValueChangeListener(event -> {
if (event.getOldValue() != null) {
final Team selectedTeam = event.getValue();
updateEmployeeField(selectedTeam);
}
});
}
private void updateEmployeeField(final Team selectedTeam) {
if (selectedTeam != null) {
final List<Employee> employeesInTeam = employeeService.findAllEmployees().stream()
.filter(employee -> employee.getTeam() != null && employee.getTeam().equals(selectedTeam))
.toList();
employee.setItems(employeesInTeam);
employee.setValue(null);
}
}
private void initializeEmployeeField() {
final List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
employee.setWidthFull();
employee.setItems(employees);
employee.setItemLabelGenerator(TimesheetEntryView::getEmployeeFullName);
employee.setRequired(true);
}
private static String getEmployeeFullName(final Employee employee) {
return employee.getFirstName() + " " + employee.getLastName();
}
private void initializeDateField() {
final LocalDate today = LocalDate.now();
final YearMonth currentMonth = YearMonth.of(today.getYear(), today.getMonth());
final LocalDate startOfMonth = currentMonth.atDay(1);
date.setWidthFull();
date.setMin(startOfMonth);
date.setMax(today);
date.setValue(today);
}
private void saveHoursWorked(final TimesheetEntry timesheetEntry) {
if (isFormValid()) {
timesheetService.save(timesheetEntry);
closeForm();
}
}
private void closeForm() {
getUI().ifPresent(ui -> ui.navigate(TimesheetListView.class));
}
private boolean isFormValid() {
return date.getValue() != null
&& team.getValue() != null
&& employee.getValue() != null
&& task.getValue() != null;
}
}

View File

@ -1,250 +0,0 @@
package com.primefactorsolutions.views.timesheet;
import com.primefactorsolutions.model.Employee;
import com.primefactorsolutions.model.TimesheetEntry;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.model.Week;
import com.primefactorsolutions.service.EmployeeService;
import com.primefactorsolutions.service.TimesheetService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.primefactorsolutions.views.util.MenuBarUtils;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ComponentEventListener;
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;
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.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.grid.PagingGrid;
import java.util.*;
import java.util.stream.Collectors;
import static com.primefactorsolutions.views.Constants.PAGE_SIZE;
@SpringComponent
@PermitAll
@Scope("prototype")
@PageTitle("Registro de Horas Trabajadas")
@Route(value = "/timesheet/records", layout = MainLayout.class)
public class TimesheetListView extends BaseView {
private final TimesheetService timesheetService;
private final EmployeeService employeeService;
private final TeamService teamService;
private final PagingGrid<TimesheetEntry> timesheetPagingGrid = new PagingGrid<>();
private ComboBox<Employee> employeeFilter;
private ComboBox<Team> teamFilter;
private ComboBox<Week> weekFilter;
public TimesheetListView(final AuthenticationContext authenticationContext,
final TimesheetService timesheetService,
final EmployeeService employeeService,
final TeamService teamService) {
super(authenticationContext);
this.timesheetService = timesheetService;
this.employeeService = employeeService;
this.teamService = teamService;
initializeView();
}
private void refreshGridListHoursWorked(final Employee employee,
final Team team,
final Week week) {
timesheetPagingGrid.setPagingDataProvider((page, pageSize) -> {
final int start = (int) (page * timesheetPagingGrid.getPageSize());
return fetchFilteredHoursWorked(start, pageSize, employee, team, week);
});
timesheetPagingGrid.getDataProvider().refreshAll();
}
private List<TimesheetEntry> fetchFilteredHoursWorked(final int start,
final int pageSize,
final Employee employee,
final Team team,
final Week week) {
List<TimesheetEntry> filteredTimesheetEntry = timesheetService.findListHoursWorkedEmployee(week.from(),
week.to());
if (employee != null) {
filteredTimesheetEntry = filteredTimesheetEntry.stream()
.filter(e -> e.getEmployee().getId().equals(employee.getId()))
.collect(Collectors.toList());
}
if (team != null) {
filteredTimesheetEntry = filteredTimesheetEntry.stream()
.filter(e -> e.getEmployee().getTeam() != null
&& e.getEmployee().getTeam().getId().equals(team.getId()))
.collect(Collectors.toList());
}
int end = Math.min(start + pageSize, filteredTimesheetEntry.size());
return filteredTimesheetEntry.subList(start, end);
}
private void initializeView() {
getCurrentPageLayout().add(createAddHoursWorked());
setupFilters();
setupListHoursWorkedGrid();
getCurrentPageLayout().add(timesheetPagingGrid);
refreshGridListHoursWorked(employeeFilter.getValue(), teamFilter.getValue(), weekFilter.getValue());
}
private void setupFilters() {
final HorizontalLayout hl = new HorizontalLayout();
hl.add(createWeekFilter());
hl.add(createEmployeeFilter());
hl.add(createTeamFilter());
getCurrentPageLayout().add(hl);
}
private void setupListHoursWorkedGrid() {
timesheetPagingGrid.addColumn(e -> e.getDate() != null ? e.getDate().toString() : "")
.setHeader("Fecha")
.setSortable(true);
timesheetPagingGrid.addColumn(e -> e.getEmployee().getFirstName() + " " + e.getEmployee().getLastName())
.setHeader("Empleado");
timesheetPagingGrid.addColumn(e -> e.getEmployee().getTeam() != null
? e.getEmployee().getTeam().getName()
: "Sin asignar")
.setHeader("Equipo");
timesheetPagingGrid.addColumn(e -> {
String details = e.getDetails() != null ? e.getDetails() : "Sin Detalle";
String task = e.getTask() != null ? e.getTask() : "";
return !task.isEmpty() ? task : details;
}).setHeader("Details");
timesheetPagingGrid.addColumn(TimesheetEntry::getHours).setHeader("Horas").setSortable(true);
timesheetPagingGrid.addComponentColumn((ValueProvider<TimesheetEntry, Component>) entry -> {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.EYE, "Ver");
viewItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
navigateToTimesheetEntryView(entry.getId(), "view");
});
final MenuItem editItem = MenuBarUtils.createIconItem(menuBar, VaadinIcon.PENCIL, "Editar");
editItem.addClickListener((ComponentEventListener<ClickEvent<MenuItem>>) menuItemClickEvent -> {
navigateToTimesheetEntryView(entry.getId(), "edit");
});
return menuBar;
});
timesheetPagingGrid.setPaginationBarMode(PagingGrid.PaginationBarMode.BOTTOM);
timesheetPagingGrid.setPageSize(PAGE_SIZE);
}
private void navigateToTimesheetEntryView(final UUID idRecord, final String action) {
getUI().ifPresent(ui -> ui.navigate(TimesheetEntryView.class, idRecord.toString() + "/" + action));
}
private Button createButton(final String label, final Runnable onClickAction, final boolean isPrimary) {
final Button button = new Button(label);
if (isPrimary) {
button.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
}
button.addClickListener(event -> onClickAction.run());
return button;
}
private Button createAddHoursWorked() {
return createButton("Agregar Actividad", this::navigateToHours, true);
}
private void navigateToHours() {
getUI().ifPresent(ui -> ui.navigate(TimesheetEntryView.class, "new"));
}
private ComboBox<Employee> createEmployeeFilter() {
employeeFilter = new ComboBox<>("Empleado");
employeeFilter.setPlaceholder("Seleccionar ...");
employeeFilter.setClearButtonVisible(true);
List<Employee> employees = new ArrayList<>(employeeService.findAllEmployees());
if (!isRoleAdmin()) {
employeeFilter.setReadOnly(true);
employees = employees.stream()
.filter(e -> getEmployeeId().equals(Optional.ofNullable(e.getId())))
.toList();
}
employeeFilter.setItems(employees);
if (!isRoleAdmin()) {
employeeFilter.setValue(employees.getFirst());
}
employeeFilter.setItemLabelGenerator(this::getEmployeeFullName);
employeeFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
event.getValue(),
teamFilter.getValue(),
weekFilter.getValue()
)
);
return employeeFilter;
}
private String getEmployeeFullName(final Employee employee) {
return employee.getFirstName() + " " + employee.getLastName();
}
private ComboBox<Team> createTeamFilter() {
teamFilter = new ComboBox<>("Equipo");
final List<Team> teams = new ArrayList<>(teamService.findAllTeams());
teamFilter.setPlaceholder("Seleccionar ...");
teamFilter.setClearButtonVisible(true);
teamFilter.setItems(teams);
teamFilter.setItemLabelGenerator(this::getTeamLabel);
teamFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
employeeFilter.getValue(),
event.getValue(),
weekFilter.getValue()
)
);
return teamFilter;
}
private ComboBox<Week> createWeekFilter() {
final List<Week> weeks = Week.getLastWeeks(4);
weekFilter = new ComboBox<>("Week");
weekFilter.setItems(weeks);
weekFilter.setValue(weeks.getFirst());
weekFilter.addValueChangeListener(event ->
refreshGridListHoursWorked(
employeeFilter.getValue(),
teamFilter.getValue(),
event.getValue()
)
);
return weekFilter;
}
private String getTeamLabel(final Team team) {
return team.getName();
}
}

View File

@ -1,143 +0,0 @@
package com.primefactorsolutions.views.timesheet;
import com.primefactorsolutions.model.TimesheetEntry;
import com.primefactorsolutions.model.Team;
import com.primefactorsolutions.model.Week;
import com.primefactorsolutions.service.TimesheetService;
import com.primefactorsolutions.service.ReportService;
import com.primefactorsolutions.service.TeamService;
import com.primefactorsolutions.views.BaseView;
import com.primefactorsolutions.views.MainLayout;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.combobox.ComboBox;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Anchor;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.spring.security.AuthenticationContext;
import jakarta.annotation.security.RolesAllowed;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.ByteArrayInputStream;
import java.time.LocalDate;
import java.time.temporal.IsoFields;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Route(value = "/timesheet/report", layout = MainLayout.class)
@PageTitle("Reporte de Horas Trabajadas")
@RolesAllowed("ROLE_ADMIN")
@Slf4j
public class TimesheetReportView extends BaseView {
private final TimesheetService timesheetService;
private final ReportService reportService;
private final ComboBox<Team> teamComboBox = new ComboBox<>("Equipo");
private final ComboBox<Week> weekComboBox = new ComboBox<>("Semana");
private final Grid<Map<String, Object>> grid = new Grid<>();
private Anchor downloadLink;
private final int currentYear = LocalDate.now().getYear();
@Autowired
public TimesheetReportView(final AuthenticationContext authenticationContext,
final TimesheetService timesheetService,
final ReportService reportService,
final TeamService teamService) {
super(authenticationContext);
this.timesheetService = timesheetService;
this.reportService = reportService;
final List<Team> teams = teamService.findAllTeams();
teamComboBox.setPlaceholder("Seleccionar ...");
teamComboBox.setItems(teams);
teamComboBox.setItemLabelGenerator(Team::getName);
initializeWeekComboBox();
final Button reportButton = new Button("Generar Reporte de Horas Trabajadas",
event -> generateHoursWorkedReport());
getCurrentPageLayout().add(reportButton);
final HorizontalLayout filtersLayout = new HorizontalLayout(teamComboBox, weekComboBox);
getCurrentPageLayout().add(filtersLayout);
grid.addColumn(map -> map.get("Empleado")).setHeader("Empleado");
grid.addColumn(map -> map.get("Horas Trabajadas")).setHeader("Horas Trabajadas");
grid.addColumn(map -> map.get("Horas Pendientes")).setHeader("Horas Pendientes");
grid.addColumn(map -> map.get("Observaciones")).setHeader("Observaciones");
getCurrentPageLayout().add(grid);
}
private void initializeWeekComboBox() {
weekComboBox.setItems(Week.getLastWeeks(8));
weekComboBox.setPlaceholder("Seleccionar ...");
}
private void generateHoursWorkedReport() {
final Team selectedTeam = teamComboBox.getValue();
final Week selectedWeek = weekComboBox.getValue();
if (selectedTeam == null || selectedWeek == null) {
Notification.show("Por favor, selecciona un equipo y una semana para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
final List<TimesheetEntry> timesheetEntryList =
timesheetService.findListHoursWorkedEmployee(selectedWeek.from(), selectedWeek.to()).stream()
.filter(e -> e.getEmployee().getTeam().getId().equals(selectedTeam.getId()))
.toList();
if (timesheetEntryList.isEmpty()) {
Notification.show("No hay horas trabajadas disponibles para generar el reporte.",
3000, Notification.Position.MIDDLE);
return;
}
final List<Map<String, Object>> data = timesheetEntryList.stream()
.map(e -> {
Map<String, Object> map = new HashMap<>();
map.put("ID", e.getId().toString());
map.put("Employee ID", e.getEmployee().getId().toString());
map.put("Empleado", e.getEmployee().getFirstName() + " "
+ e.getEmployee().getLastName());
map.put("Horas Trabajadas", e.getHours());
map.put("Horas Pendientes", 40 - e.getHours());
map.put("Observaciones", "");
return map;
})
.collect(Collectors.toList());
grid.setItems(data);
int weekNumber = weekComboBox.getValue().from().get(IsoFields.WEEK_OF_WEEK_BASED_YEAR);
generateExcelDownloadLink(data, weekNumber);
}
private void generateExcelDownloadLink(final List<Map<String, Object>> data, final int weekNumber) {
try {
final List<String> headers = List.of("Empleado", "Horas Trabajadas", "Horas Pendientes", "Observaciones");
final String selectedTeam = teamComboBox.getValue().getName();
final byte[] excelBytes = reportService.writeAsExcel(
"hours_worked_report", headers, data, selectedTeam, weekNumber, currentYear);
final StreamResource excelResource = new StreamResource("hours_worked_report.xlsx",
() -> new ByteArrayInputStream(excelBytes));
if (downloadLink == null) {
downloadLink = new Anchor(excelResource, "Descargar Reporte en Excel");
downloadLink.getElement().setAttribute("download", true);
getCurrentPageLayout().add(downloadLink);
} else {
downloadLink.setHref(excelResource);
}
} catch (Exception e) {
Notification.show("Error al generar el reporte de horas trabajadas en Excel.",
3000, Notification.Position.MIDDLE);
log.error("Error generating report", e);
}
}
}

View File

@ -1,32 +0,0 @@
package com.primefactorsolutions.views.util;
import com.primefactorsolutions.model.Employee;
import com.vaadin.flow.spring.security.AuthenticationContext;
import lombok.experimental.UtilityClass;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Optional;
import java.util.UUID;
@UtilityClass
public class AuthUtils {
public static boolean isUser(final AuthenticationContext authenticationContext) {
return authenticationContext.getAuthenticatedUser(UserDetails.class).isPresent();
}
public static boolean isAdmin(final AuthenticationContext authenticationContext) {
return authenticationContext.getAuthenticatedUser(UserDetails.class)
.map(u ->
u.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.anyMatch("ROLE_ADMIN"::equals)).orElse(false);
}
public static Optional<UUID> getEmployeeId(final AuthenticationContext authenticationContext) {
return authenticationContext.getAuthenticatedUser(UserDetails.class)
.map(u -> u instanceof Employee
? ((Employee) u).getId()
: null);
}
}

View File

@ -1,13 +0,0 @@
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;
}
}

View File

@ -1,18 +0,0 @@
package com.primefactorsolutions.views.util;
import com.primefactorsolutions.model.HasLabel;
import org.vaadin.firitin.components.combobox.VComboBox;
import java.util.List;
public class EntityComboBox<T extends HasLabel> extends VComboBox<T> {
public EntityComboBox(final String label) {
super(label);
this.setItemLabelGenerator(HasLabel::getLabel);
}
public EntityComboBox(final String label, final List<T> items) {
super(label, items);
this.setItemLabelGenerator(HasLabel::getLabel);
}
}

View File

@ -1,26 +1,13 @@
package com.primefactorsolutions.views.util;
import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.ComponentEventListener;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.menubar.MenuBar;
import com.vaadin.flow.component.menubar.MenuBarVariant;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
@UtilityClass
public class MenuBarUtils {
public static final Map<String, VaadinIcon> ICON_MAP = Map.of(
"View", VaadinIcon.EYE,
"Edit", VaadinIcon.PENCIL,
"Copy", VaadinIcon.COPY,
"Email", VaadinIcon.ENVELOPE,
"Download", VaadinIcon.DOWNLOAD
);
public static MenuItem createIconItem(final MenuBar menu, final VaadinIcon iconName, final String ariaLabel) {
final Icon icon = new Icon(iconName);
@ -29,31 +16,4 @@ public class MenuBarUtils {
return item;
}
public static MenuBar menuBar(final Map<Pair<String, VaadinIcon>,
ComponentEventListener<ClickEvent<MenuItem>>> entries) {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
entries.forEach((key, value) -> {
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar, key.getValue(), key.getKey());
viewItem.addClickListener(value);
});
return menuBar;
}
@SafeVarargs
public static MenuBar menuBar(final Pair<String, ComponentEventListener<ClickEvent<MenuItem>>>... entries) {
final MenuBar menuBar = new MenuBar();
menuBar.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE);
for (Pair<String, ComponentEventListener<ClickEvent<MenuItem>>> entry: entries) {
final MenuItem viewItem = MenuBarUtils.createIconItem(menuBar,
ICON_MAP.get(entry.getKey()), entry.getKey());
viewItem.addClickListener(entry.getValue());
}
return menuBar;
}
}

View File

@ -6,7 +6,7 @@ spring.mustache.check-template-location = false
vaadin.launch-browser=true
# To improve the performance during development.
# For more information https://vaadin.com/docs/latest/integrations/spring/configuration#special-configuration-parameters
vaadin.allowed-packages = com.vaadin,org.vaadin,com.primefactorsolutions,com.hilerio.ace,com.flowingcode.vaadin,org.vaadin.firitin,org.vaadin.addons.stefan,io.overcoded
vaadin.allowed-packages = com.vaadin,org.vaadin,com.primefactorsolutions,com.hilerio.ace,com.flowingcode.vaadin,org.vaadin.firitin,org.vaadin.addons.stefan
spring.mail.host=smtp.primefactorsolutions.com
spring.mail.username=${SMTP_USER}

View File

@ -3,95 +3,69 @@ insert into candidate (id, version, email) values ('23471ab3-f639-4d2b-9541-7227
insert into question (id, version, content, title, description) values ('a7e00ff8-da41-4624-b31c-1b13c3f2e3ae', 1, 'foo bar', 'q1', 'lorem ipsum');
insert into question (id, version, content, title, description) values ('8a4b213c-ca81-4c38-b56d-d7028c2dde88', 1, 'foo buzz', 'q2', 'lorem ipsum');
insert into exam (id, version, candidate_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 1, '23471ab3-f639-4d2b-9541-7227f4ea7ee6');
insert into assessment(id, version, candidate_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 1, '23471ab3-f639-4d2b-9541-7227f4ea7ee6');
insert into exam_questions (exam_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae');
insert into exam_questions (exam_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', '8a4b213c-ca81-4c38-b56d-d7028c2dde88');
insert into ASSESSMENT_QUESTIONS (assessment_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae');
insert into team (id, version, name) values ('b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 1, 'ABC');
insert into team (id, version, name) values ('6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 1, 'XYZ');
insert into team (id, version, name) values ('c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 1, 'DEF');
insert into team (id, version, name) values ('8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 1, 'GHI');
insert into ASSESSMENT_QUESTIONS (assessment_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', '8a4b213c-ca81-4c38-b56d-d7028c2dde88');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('123e4567-e89b-12d3-a456-426614174000', 1, 'AÑO_NUEVO', '2024-1-1', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('223e4567-e89b-12d3-a456-426614174001', 1, 'LUNES_CARNAVAL', '2024-2-12', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('323e4567-e89b-12d3-a456-426614174002', 1, 'MARTES_CARNAVAL', '2024-2-13', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('423e4567-e89b-12d3-a456-426614174003', 1, 'VIERNES_SANTO', '2024-3-29', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('523e4567-e89b-12d3-a456-426614174004', 1, 'DIA_DEL_TRABAJADOR', '2024-5-1', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('623e4567-e89b-12d3-a456-426614174005', 1, 'DIA_DE_LA_INDEPENDENCIA', '2024-8-6', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('723e4567-e89b-12d3-a456-426614174006', 1, 'NAVIDAD', '2024-12-25', 1, 1, 'FIXED');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('823e4567-e89b-12d3-a456-426614174007', 1, 'DIA_DEL_ESTADO_PLURINACIONAL', '2024-1-21', 1, 30, 'MOVABLE');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('923e4567-e89b-12d3-a456-426614174008', 1, 'CORPUS_CHRISTI', '2024-5-30', 1, 30, 'MOVABLE');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('a23e4567-e89b-12d3-a456-426614174009', 1, 'AÑO_NUEVO_ANDINO', '2024-6-21', 1, 30, 'MOVABLE');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('b23e4567-e89b-12d3-a456-42661417400a', 1, 'ANIVERSARIO_DEPARTAMENTAL', '2024-9-14', 1, 30, 'MOVABLE');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('c23e4567-e89b-12d3-a456-42661417400b', 1, 'DIA_DE_TODOS_LOS_DIFUNTOS', '2024-11-2', 1, 30, 'MOVABLE');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('c23e4567-e89b-12d3-a456-42661417401a', 1, 'DIA_DEL_PADRE', '2024-3-19', 0.5, 30, 'OTHER');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('c23e4567-e89b-12d3-a456-42661417401b', 1, 'DIA_DE_LA_MADRE', '2024-5-27', 0.5, 30, 'OTHER');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('c23e4567-e89b-12d3-a456-42661417401c', 1, 'DIA_DE_LA_MUJER_INTERNACIONAL', '2024-3-8', 0.5, 30, 'OTHER');
insert into time_off (id, version, category, date, duration, expiration, type)
values ('c23e4567-e89b-12d3-a456-42661417401d', 1, 'DIA_DE_LA_MUJER_NACIONAL', '2024-10-11', 0.5, 30, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417400c', 1, 'CUMPLEAÑOS', 0.5, 0.5, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417400d', 1, 'MATERNIDAD', 90, 90, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417400e', 1, 'PATERNIDAD', 3, 3, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417400f', 1, 'MATRIMONIO', 3, 3, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('550e8400-e29b-41d4-a716-446655440000', 1, 'DUELO_1ER_GRADO', 3, 3, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417400a', 1, 'DUELO_2ER_GRADO', 2, 2, 'OTHER');
insert into time_off (id, version, category, duration, expiration, type) values ('c23e4567-e89b-12d3-a456-42661417401e', 1, 'PERMISOS_DE_SALUD', 2, 360, 'OTHER');
insert into time_off (id, version, category, expiration, type) values ('490e5fbe-895b-42f8-b914-95437f7b39c0', 1, 'VACACION_GESTION_ACTUAL', 360, 'OTHER');
insert into time_off (id, version, category, expiration, type) values ('c23e4567-e89b-12d3-a456-4266141740ff', 1, 'VACACION_GESTION_ANTERIOR', 360, 'OTHER');
INSERT INTO team (id, version, name) VALUES ('b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 1, 'ABC');
INSERT INTO team (id, version, name) VALUES ('6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 1, 'XYZ');
INSERT INTO team (id, version, name) VALUES ('c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 1, 'DEF');
INSERT INTO team (id, version, name) VALUES ('8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 1, 'GHI');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('123e4567-e89b-12d3-a456-426614174000', 1, 'AÑO_NUEVO', 1, 1, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('223e4567-e89b-12d3-a456-426614174001', 1, 'LUNES_CARNAVAL', 2, 12, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('323e4567-e89b-12d3-a456-426614174002', 1, 'MARTES_CARNAVAL', 2, 13, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('423e4567-e89b-12d3-a456-426614174003', 1, 'VIERNES_SANTO', 3, 29, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('523e4567-e89b-12d3-a456-426614174004', 1, 'DIA_DEL_TRABAJADOR', 5, 1, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('623e4567-e89b-12d3-a456-426614174005', 1, 'DIA_DE_LA_INDEPENDENCIA', 8, 6, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('723e4567-e89b-12d3-a456-426614174006', 1, 'NAVIDAD', 12, 25, 1, 1, 'FIXED');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('823e4567-e89b-12d3-a456-426614174007', 1, 'DIA_DEL_ESTADO_PLURINACIONAL', 1, 21, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('923e4567-e89b-12d3-a456-426614174008', 1, 'CORPUS_CHRISTI', 5, 30, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('a23e4567-e89b-12d3-a456-426614174009', 1, 'AÑO_NUEVO_ANDINO', 6, 21, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('b23e4567-e89b-12d3-a456-42661417400a', 1, 'ANIVERSARIO_DEPARTAMENTAL', 9, 14, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400b', 1, 'DIA_DE_TODOS_LOS_DIFUNTOS', 11, 2, 1, 30, 'MOVABLE');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400c', 1, 'CUMPLEAÑOS', 12, 31, 0.5, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400d', 1, 'MATERNIDAD', 90, 90, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400e', 1, 'PATERNIDAD', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400f', 1, 'MATRIMONIO', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('550e8400-e29b-41d4-a716-446655440000', 1, 'DUELO_1ER_GRADO', 3, 3, 'OTHER');
INSERT INTO vacation (id, version, category, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417400a', 1, 'DUELO_2ER_GRADO', 2, 2, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401a', 1, 'DIA_DEL_PADRE', 3, 19, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401b', 1, 'DIA_DE_LA_MADRE', 5, 27, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401c', 1, 'DIA_DE_LA_MUJER_INTERNACIONAL', 3, 8, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, month_of_year, day_of_month, duration, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401d', 1, 'DIA_DE_LA_MUJER_NACIONAL', 10, 11, 0.5, 30, 'OTHER');
INSERT INTO vacation (id, version, category, duration, type) VALUES ('c23e4567-e89b-12d3-a456-42661417401e', 1, 'PERMISOS_DE_SALUD', 2, 'OTHER');
INSERT INTO vacation (id, version, category, expiration, type) VALUES ('490e5fbe-895b-42f8-b914-95437f7b39c0', 1, 'VACACION_GESTION_ACTUAL', 360, 'OTHER');
INSERT INTO vacation (id, version, category, expiration, type) VALUES ('c23e4567-e89b-12d3-a456-4266141740ff', 1, 'VACACION_GESTION_ANTERIOR', 360, 'OTHER');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, birthday, date_of_entry) values ('5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 1, 'bob', 'Bob', 'Test', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE', '2024-02-20', '2013-10-22');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('cba3efb7-32bc-44be-9fdc-fc5e4f211254', 1, 'ben', 'Ben', 'Test', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'MALE', '2016-10-23');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 1, 'jperez', 'Juan', 'Perez Condori', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE', '2022-10-22');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry) values ('f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 1, 'agarcia', 'Ana', 'Garcia Rojas', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE', '2024-10-24');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 1, 'clopez', 'Carlos', 'Lopez Mendoza', 'INACTIVE', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 1, 'mfernandez', 'Maria', 'Fernandez Villca', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'lgutierrez', 'Luis', 'Gutierrez Mamani', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 1, 'lmartinez', 'Laura', 'Martinez Paredes', 'INACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('6e6a8a4e-9f6b-44eb-8c69-40acfdc86756', 1, 'rsantos', 'Roberto', 'Santos Escobar', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('36b0d1c6-bdc0-4d98-94bb-08b9bce3f0d5', 1, 'vmorales', 'Valeria', 'Morales Ochoa', 'INACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('5a1c6d80-58b3-43e3-a5a5-24b4a2d1d54a', 1, 'jramirez', 'Jorge', 'Ramirez Tapia', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('9d6a5b2e-6d0b-4b89-8d6a-d3f3d1bfc047', 1, 'storres', 'Sandra', 'Torres Huanca', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('f8b3e0c0-0d5a-4e5c-bf9d-207b9b5e8279', 1, 'fquispe', 'Felipe', 'Quispe Huanca', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 1, 'grivas', 'Gabriela', 'Rivas Arana', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('62d3c1b7-815e-4e96-8d7e-f8c4236bca55', 1, 'oflores', 'Oscar', 'Flores Quiroga', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('f20b7c5a-5a67-44f0-9ec1-4c1b8e80de05', 1, 'mvargas', 'Marta', 'Vargas Soria', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('19b5a76e-d7b1-4b76-8b02-4d0748e85809', 1, 'aespinoza', 'Andres', 'Espinoza Chura', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender) values ('5c1a7b82-832d-4f24-8377-54b77b91b6a8', 1, 'cvillanueva', 'Carla', 'Villanueva Arce', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, birthday, date_of_entry, lead_manager, role)
values ('5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 1, 'bob', 'Bob', 'Test', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE', '2024-02-20', '2013-10-22', 'ben', 'ADMIN');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry, role)
values ('cba3efb7-32bc-44be-9fdc-fc5e4f211254', 1, 'ben', 'Ben', 'Test', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'MALE', '2016-10-23', 'USER');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry)
values ('e99b7af5-7d3a-4c0f-b8bc-e8d0388d8fc4', 1, 'jperez', 'Juan', 'Perez Condori', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE', '2022-10-22');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender, date_of_entry)
values ('f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 1, 'agarcia', 'Ana', 'Garcia Rojas', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE', '2024-10-24');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('2e2293b1-3f9a-4f3d-abc8-32639b0a5e15', 1, 'clopez', 'Carlos', 'Lopez Mendoza', 'INACTIVE', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('4b1c6c35-4627-4b35-b6e9-dc75c68b2c31', 1, 'mfernandez', 'Maria', 'Fernandez Villca', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('afc5c741-f70a-4394-853b-39d51b118927', 1, 'lgutierrez', 'Luis', 'Gutierrez Mamani', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('b2436b82-7b9f-4f0d-9463-f2c3173a45c3', 1, 'lmartinez', 'Laura', 'Martinez Paredes', 'INACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('6e6a8a4e-9f6b-44eb-8c69-40acfdc86756', 1, 'rsantos', 'Roberto', 'Santos Escobar', 'ACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('36b0d1c6-bdc0-4d98-94bb-08b9bce3f0d5', 1, 'vmorales', 'Valeria', 'Morales Ochoa', 'INACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('5a1c6d80-58b3-43e3-a5a5-24b4a2d1d54a', 1, 'jramirez', 'Jorge', 'Ramirez Tapia', 'ACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('9d6a5b2e-6d0b-4b89-8d6a-d3f3d1bfc047', 1, 'storres', 'Sandra', 'Torres Huanca', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('f8b3e0c0-0d5a-4e5c-bf9d-207b9b5e8279', 1, 'fquispe', 'Felipe', 'Quispe Huanca', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('cd80e1d0-9a08-44a6-bd63-2c63eaa003d4', 1, 'grivas', 'Gabriela', 'Rivas Arana', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('62d3c1b7-815e-4e96-8d7e-f8c4236bca55', 1, 'oflores', 'Oscar', 'Flores Quiroga', 'INACTIVE', 'c3a8a7b1-f2d9-48c0-86ea-f215c2e6b3a3', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('f20b7c5a-5a67-44f0-9ec1-4c1b8e80de05', 1, 'mvargas', 'Marta', 'Vargas Soria', 'ACTIVE', '8f6b61e7-efb2-4de7-b8ed-7438c9d8babe', 'FEMALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('19b5a76e-d7b1-4b76-8b02-4d0748e85809', 1, 'aespinoza', 'Andres', 'Espinoza Chura', 'INACTIVE','b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa', 'MALE');
insert into employee (id, version, username, first_name, last_name, status, team_id, gender)
values ('5c1a7b82-832d-4f24-8377-54b77b91b6a8', 1, 'cvillanueva', 'Carla', 'Villanueva Arce', 'ACTIVE', '6d63bc15-3f8b-46f7-9cf1-7e9b0b9a2b28', 'FEMALE');
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('9d6f12ba-e341-4e7a-b8a6-cab0982bd8c1', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'PATERNIDAD', 'APROBADO', 3, '2024-10-03', '2024-10-01', '2024-10-03', 3, 0);
@ -104,7 +78,7 @@ values ('9c6f12ba-e333-4e7a-b8a6-caa0982bd8c3', 1, '5c6f11fe-c341-4be7-a9a6-bba0
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('9d6f12ba-e444-4e7a-b8a6-caa0982bd8d4', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VIERNES_SANTO', 'APROBADO', 1, '2024-03-29', '2024-03-29', '2024-03-29', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('9e6f12ba-e555-4e7a-b8a6-caa0982bd8e5', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VACACION_GESTION_ANTERIOR', 'APROBADO', 20, '2024-11-01', '2023-11-01', '2023-11-30', 20, 0);
values ('9e6f12ba-e555-4e7a-b8a6-caa0982bd8e5', 1, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'VACACION_GESTION_ANTERIOR', 'APROBADO', 20, '2024-11-01', '2022-11-01', '2022-11-30', 20, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('8c653f2a-f9a3-4d67-b3b6-12ad98fe0983', 1, 'f6ab3c6d-7078-45f6-9b22-4e37637bfec6', 'DIA_DEL_TRABAJADOR', 'APROBADO', 1, '2024-05-01', '2024-05-01', '2024-05-01', 1, 0);
@ -116,6 +90,3 @@ insert into time_off_request (id, version, employee_id, category, state, availab
values ('12ec8b74-983d-4a17-b67e-134f45ae904c', 1, '5c1a7b82-832d-4f24-8377-54b77b91b6a8', 'AÑO_NUEVO', 'SOLICITADO', 1, '2025-01-01', '2025-01-01', '2025-01-01', 1, 0);
insert into time_off_request (id, version, employee_id, category, state, available_days, expiration, start_date, end_date, days_to_be_take, days_balance)
values ('89bc4b2a-943f-487c-a9f3-bacf78145e67', 1, 'cba3efb7-32bc-44be-9fdc-fc5e4f211254', 'LUNES_CARNAVAL', 'APROBADO', 1, '2024-02-12', '2024-02-12', '2024-02-12', 1, 0);
insert into timesheet_entry (id, version, task, details, date, hours, employee_id, team_id)
values ('389389ce-7b2e-4f39-aa06-2a251a2b35ea', 0, 'coding', 'meet', '2024-12-30', 4, '5c6f11fe-c341-4be7-a9a6-bba0081ad7c6', 'b0e8f394-78c1-4d8a-9c57-dc6e8b36a5fa');

View File

@ -2,7 +2,7 @@ package com.primefactorsolutions;
import java.util.*;
import java.lang.reflect.*;
import java.util.stream.*;
import java.util.stream.Collectors;
public class TestClass {
@ -12,9 +12,9 @@ public class TestClass {
return "TODO";
}
// ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
// PRUEBAS UNITARIAS
public static class Tests {
public static Map.Entry<String, Boolean> testNInvalido() {
boolean result = getFooBar(-1).equals("");
@ -29,7 +29,7 @@ public class TestClass {
}
}
public static Map<String, Boolean> run () {
public static Map<String, Boolean> run() {
Map<String, Boolean> results = Arrays.stream(Tests.class.getDeclaredMethods())
.map(m -> {
try {

View File

@ -2,48 +2,48 @@ package com.primefactorsolutions;
import java.util.*;
import java.lang.reflect.*;
import java.util.stream.*;
import java.util.stream.Collectors;
public class TestClass {
// ------------ IMPLEMENTAR AQUI --------------------------------
public static String getPalindromo(String s) {
public static String getPalindroma(String s) {
return "TODO";
}
// ------------- NO MODIFICAR DESDE ESTA LINEA --------------------
// PRUEBAS UNITARIAS
public static class Tests {
public static Map.Entry<String, Boolean> testPalindromoValido() {
boolean result = getPalindromo("Ab").equals("ABBA");
public static Map.Entry<String, Boolean> testPalindromaValido() {
boolean result = getPalindroma("Ab").equals("ABBA");
return Map.entry("palindromo es valido", result);
return Map.entry("palindrome es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromoVacio() {
boolean result = getPalindromo("").equals("");
public static Map.Entry<String, Boolean> testPalindromaVacio() {
boolean result = getPalindroma("").equals("");
return Map.entry("palindromo vacio es valido", result);
return Map.entry("palindrome vacio es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromoUnico() {
boolean result = getPalindromo("z").equals("Z");
public static Map.Entry<String, Boolean> testPalindromaUnico() {
boolean result = getPalindroma("z").equals("Z");
return Map.entry("palindromo un solo caracter es valido", result);
return Map.entry("palindrome un solo caracter es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromoEntrada() {
boolean result = getPalindromo("abba").equals("ABBA");
public static Map.Entry<String, Boolean> testPalindromaUnico() {
boolean result = getPalindroma("abba").equals("ABBA");
return Map.entry("palindromo en entrada es valido", result);
return Map.entry("palindrome un solo caracter es valido", result);
}
public static Map.Entry<String, Boolean> testPalindromoNull() {
boolean result = getPalindromo(null) == null;
public static Map.Entry<String, Boolean> testPalindromaNull() {
boolean result = getPalindroma(null) == null;
return Map.entry("palindromo null es valido", result);
return Map.entry("palindrome null es valido", result);
}
}

Some files were not shown because too many files have changed in this diff Show More