management model

This commit is contained in:
alex 2024-08-11 13:35:43 -04:00
parent e01e1701b6
commit 9f03c04b61
28 changed files with 292 additions and 96 deletions

View File

@ -25,7 +25,7 @@ public class Assessment extends BaseEntity {
private List<Submission> submissions = new ArrayList<>();
@ManyToOne
private AppUser appUser;
private Candidate candidate;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "assessment", cascade = {CascadeType.ALL})
private List<AssessmentEvent> assessmentEvents = new ArrayList<>();

View File

@ -13,10 +13,10 @@ import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class AppUser extends BaseEntity {
public class Candidate extends BaseEntity {
@Column(unique = true)
private String email;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "appUser")
@OneToMany(fetch = FetchType.EAGER, mappedBy = "candidate")
private List<Assessment> assessments;
}

View File

@ -0,0 +1,23 @@
package com.primefactorsolutions.model;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Document extends BaseEntity {
@Nullable
private String description;
private String location;
private DocumentType documentType;
@ManyToOne
private Employee employee;
}

View File

@ -0,0 +1,7 @@
package com.primefactorsolutions.model;
public enum DocumentType {
ID_CARD,
PAY_STUB,
OTHER
}

View File

@ -0,0 +1,31 @@
package com.primefactorsolutions.model;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.Type;
import java.time.LocalDate;
import java.util.List;
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Employee extends BaseEntity {
private String username;
private String firstName;
private String lastName;
private LocalDate dob;
private String personalEmail;
@Type(JsonType.class)
@Column(columnDefinition = "json")
private List<String> phoneNumbers;
@OneToMany(fetch = FetchType.EAGER, mappedBy = "employee", cascade = {CascadeType.ALL})
private List<Document> documents;
private Role role;
}

View File

@ -0,0 +1,6 @@
package com.primefactorsolutions.model;
public enum PartOfDay {
MORNING,
AFTERNOON
}

View File

@ -0,0 +1,7 @@
package com.primefactorsolutions.model;
public enum Role {
ADMIN,
ACCOUNTING,
USER
}

View File

@ -0,0 +1,17 @@
package com.primefactorsolutions.model;
import jakarta.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TaskType extends BaseEntity {
private String description;
private int code;
}

View File

@ -0,0 +1,21 @@
package com.primefactorsolutions.model;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TimeOffRequest extends BaseEntity {
@OneToMany(fetch = FetchType.EAGER, mappedBy = "request", cascade = {CascadeType.ALL})
private List<TimeOffRequestEntry> entries;
private TimeOffRequestType type;
}

View File

@ -0,0 +1,22 @@
package com.primefactorsolutions.model;
import jakarta.annotation.Nullable;
import jakarta.persistence.Entity;
import jakarta.persistence.ManyToOne;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
@Entity
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class TimeOffRequestEntry extends BaseEntity {
private LocalDate date;
@Nullable
private PartOfDay partOfDay;
@ManyToOne
private TimeOffRequest request;
}

View File

@ -0,0 +1,7 @@
package com.primefactorsolutions.model;
public enum TimeOffRequestType {
VACATION,
MATERNITY,
OTHER
}

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

@ -0,0 +1,13 @@
package com.primefactorsolutions.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TimesheetEntry {
private TaskType taskType;
private int hours;
}

View File

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

View File

@ -38,7 +38,7 @@ public class AssessmentService {
final SimpleMailMessage message = new SimpleMailMessage();
message.setFrom("no-reply@primefactorsolutions.com");
message.setBcc("no-reply@primefactorsolutions.com");
message.setTo(assessment.getAppUser().getEmail());
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 "
@ -47,9 +47,9 @@ public class AssessmentService {
+ "Exito!"));
emailSender.send(message);
log.info("Sent email to {}", assessment.getAppUser().getEmail());
log.info("Sent email to {}", assessment.getCandidate().getEmail());
} catch (Exception e) {
log.error("Error sending email to {}", assessment.getAppUser().getEmail(), e);
log.error("Error sending email to {}", assessment.getCandidate().getEmail(), e);
throw e;
}
}
@ -181,11 +181,11 @@ public class AssessmentService {
@Transactional
public Assessment saveAssessment(final Assessment assessment) {
AppUser merged = entityManager.merge(assessment.getAppUser());
Candidate merged = entityManager.merge(assessment.getCandidate());
List<Question> mergedQuestions = assessment.getQuestions().stream().map(entityManager::merge)
.collect(Collectors.toList());
assessment.setAppUser(merged);
assessment.setCandidate(merged);
assessment.setQuestions(mergedQuestions);
return assessmentRepository.save(assessment);

View File

@ -0,0 +1,29 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.repositories.CandidateRepository;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
public class CandidateService {
private final CandidateRepository candidateRepository;
public Candidate createOrUpdate(final Candidate assessment) {
final Candidate saved = candidateRepository.save(assessment);
return saved;
}
public List<Candidate> getCandidates() {
return candidateRepository.findAll();
}
public Candidate getCandidate(final UUID id) {
return candidateRepository.findById(id).orElse(null);
}
}

View File

@ -1,29 +0,0 @@
package com.primefactorsolutions.service;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.repositories.UserRepository;
import lombok.Data;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.UUID;
@Service
@Data
public class UserService {
private final UserRepository userRepository;
public AppUser createOrUpdate(final AppUser assessment) {
final AppUser saved = userRepository.save(assessment);
return saved;
}
public List<AppUser> getUsers() {
return userRepository.findAll();
}
public AppUser getUser(final UUID id) {
return userRepository.findById(id).orElse(null);
}
}

View File

@ -1,11 +1,11 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.model.Assessment;
import com.primefactorsolutions.model.Question;
import com.primefactorsolutions.service.AssessmentService;
import com.primefactorsolutions.service.QuestionService;
import com.primefactorsolutions.service.UserService;
import com.primefactorsolutions.service.CandidateService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.ItemLabelGenerator;
import com.vaadin.flow.component.combobox.ComboBox;
@ -33,17 +33,17 @@ import java.util.UUID;
public class AssessmentView extends BeanValidationForm<Assessment> implements HasUrlParameter<String> {
private final AssessmentService assessmentService;
private ComboBox<AppUser> appUser = null;
private ComboBox<Candidate> candidate = null;
private SubListSelector<Question> questions = null;
public AssessmentView(AssessmentService assessmentService, QuestionService questionService, UserService userService) {
public AssessmentView(AssessmentService assessmentService, QuestionService questionService, CandidateService candidateService) {
super(Assessment.class);
this.assessmentService = assessmentService;
appUser = new ComboBox<>("AppUser", userService.getUsers());
appUser.setItemLabelGenerator((ItemLabelGenerator<AppUser>) AppUser::getEmail);
appUser.setReadOnly(false);
candidate = new ComboBox<>("Candidate", candidateService.getCandidates());
candidate.setItemLabelGenerator((ItemLabelGenerator<Candidate>) Candidate::getEmail);
candidate.setReadOnly(false);
questions = new SubListSelector<>(Question.class);
questions.setItemLabelGenerator((ItemLabelGenerator<Question>) Question::getTitle);
@ -69,6 +69,6 @@ public class AssessmentView extends BeanValidationForm<Assessment> implements Ha
@Override
protected List<Component> getFormComponents() {
return List.of(appUser, questions);
return List.of(candidate, questions);
}
}

View File

@ -42,7 +42,7 @@ public class AssessmentsListView extends Main {
hl.add(addAssessment);
final VGrid<Assessment> grid = new VGrid<>(Assessment.class);
grid.setColumns("id", "appUser.email");
grid.setColumns("id", "candidate.email");
final Grid.Column<Assessment> statusColumn = grid.addColumn((ValueProvider<Assessment, Object>) assessment ->
assessment.getAssessmentEvents().isEmpty()
? "N/A"
@ -61,13 +61,13 @@ public class AssessmentsListView extends Main {
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.getAppUser().getEmail(),
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.getAppUser().getEmail()));
dialog.setText(String.format("Enviar link por email al candidato %s?", assessment.getCandidate().getEmail()));
dialog.setCancelable(true);
dialog.setConfirmText("Enviar");
dialog.setConfirmButtonTheme("primary");

View File

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

View File

@ -1,7 +1,7 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.service.UserService;
import com.primefactorsolutions.model.Candidate;
import com.primefactorsolutions.service.CandidateService;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.textfield.EmailField;
import com.vaadin.flow.router.BeforeEvent;
@ -20,22 +20,22 @@ import java.util.UUID;
@SpringComponent
@Scope("prototype")
@PageTitle("Assessments")
@Route(value = "/users", layout = MainLayout.class)
@Route(value = "/candidates", layout = MainLayout.class)
@PermitAll
public class UserView extends BeanValidationForm<AppUser> implements HasUrlParameter<String> {
private final UserService userService;
public class CandidateView extends BeanValidationForm<Candidate> implements HasUrlParameter<String> {
private final CandidateService candidateService;
private EmailField email = null;
public UserView(final UserService userService) {
super(AppUser.class);
this.userService = userService;
public CandidateView(final CandidateService candidateService) {
super(Candidate.class);
this.candidateService = candidateService;
email = new EmailField();
email.setWidthFull();
email.setLabel("Email");
setSavedHandler((SavedHandler<AppUser>) appUser -> {
final AppUser saved = userService.createOrUpdate(appUser);
setSavedHandler((SavedHandler<Candidate>) candidate -> {
final Candidate saved = candidateService.createOrUpdate(candidate);
setEntityWithEnabledSave(saved);
});
}
@ -43,10 +43,10 @@ public class UserView extends BeanValidationForm<AppUser> implements HasUrlParam
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var user = userService.getUser(UUID.fromString(s));
setEntityWithEnabledSave(user);
var candidate = candidateService.getCandidate(UUID.fromString(s));
setEntityWithEnabledSave(candidate);
} else {
setEntityWithEnabledSave(new AppUser());
setEntityWithEnabledSave(new Candidate());
}
}

View File

@ -1,7 +1,7 @@
package com.primefactorsolutions.views;
import com.primefactorsolutions.model.AppUser;
import com.primefactorsolutions.service.UserService;
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;
@ -24,29 +24,29 @@ import java.util.stream.Stream;
@SpringComponent
@Scope("prototype")
@PageTitle("Users")
@Route(value = "/users", layout = MainLayout.class)
@PageTitle("Candidates")
@Route(value = "/candidates", layout = MainLayout.class)
@PermitAll
public class UsersListView extends Main {
final UserService userService;
public class CandidatesListView extends Main {
final CandidateService candidateService;
public UsersListView(final UserService userService) {
this.userService = userService;
public CandidatesListView(final CandidateService candidateService) {
this.candidateService = candidateService;
final HorizontalLayout hl = new HorizontalLayout();
final Button addUser = new Button("Add User");
addUser.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().get().navigate(UserView.class, "new");
final Button addCandidate = new Button("Add Candidate");
addCandidate.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent -> {
this.getUI().get().navigate(CandidateView.class, "new");
});
hl.add(addUser);
hl.add(addCandidate);
final VGrid<AppUser> grid = new VGrid<>(AppUser.class);
final VGrid<Candidate> grid = new VGrid<>(Candidate.class);
grid.setColumns("id", "email");
grid.setAllRowsVisible(true);
grid.addComponentColumn((ValueProvider<AppUser, Component>) appUser -> {
grid.addComponentColumn((ValueProvider<Candidate, Component>) candidate -> {
final Button edit = new Button("Edit");
edit.addClickListener((ComponentEventListener<ClickEvent<Button>>) buttonClickEvent ->
this.getUI().get().navigate(UserView.class, appUser.getId().toString()));
this.getUI().get().navigate(CandidateView.class, candidate.getId().toString()));
return edit;
});
@ -57,20 +57,20 @@ public class UsersListView extends Main {
}
@Override
public int size(Query<AppUser, Object> query) {
return userService.getUsers().size();
public int size(Query<Candidate, Object> query) {
return candidateService.getCandidates().size();
}
@Override
public Stream<AppUser> fetch(Query<AppUser, Object> query) {
public Stream<Candidate> fetch(Query<Candidate, Object> query) {
int limit = query.getLimit();
int pagerSize = query.getPageSize();
int page = query.getPage();
return userService.getUsers().stream();
return candidateService.getCandidates().stream();
}
@Override
public void refreshItem(AppUser appUser) {
public void refreshItem(Candidate candidate) {
}
@ -80,7 +80,7 @@ public class UsersListView extends Main {
}
@Override
public Registration addDataProviderListener(DataProviderListener<AppUser> dataProviderListener) {
public Registration addDataProviderListener(DataProviderListener<Candidate> dataProviderListener) {
return null;
}
});

View File

@ -254,7 +254,7 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
log.info(">>> start");
this.assessment = this.assessmentService.startAssessment(this.assessment.getId());
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getAppUser().getEmail())) {
if (tf.getValue().trim().equalsIgnoreCase(this.assessment.getCandidate().getEmail())) {
this.getUI().get().getPage().reload();
} else {
Notification notification = new Notification();
@ -389,7 +389,7 @@ public class EvaluationView extends Main implements HasUrlParameter<String> {
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add(
createItem("Candidato:", assessment.getAppUser().getEmail()),
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"))

View File

@ -54,10 +54,26 @@ public class MainLayout extends AppLayout {
SideNav nav = new SideNav();
authContext.getAuthenticatedUser(UserDetails.class).ifPresent(u -> {
SideNavItem recruiting = new SideNavItem("Recruiting", MainView.class, 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()));
SideNavItem admin = new SideNavItem("Admin", MainView.class, LineAwesomeIcon.SUITCASE_SOLID.create());
admin.addItem(new SideNavItem("Time-off requests", AssessmentsListView.class, LineAwesomeIcon.THEMEISLE.create()));
admin.addItem(new SideNavItem("Timesheets", CandidatesListView.class, LineAwesomeIcon.HOURGLASS_END_SOLID.create()));
admin.addItem(new SideNavItem("Employees", QuestionsListView.class, LineAwesomeIcon.USER_EDIT_SOLID.create()));
SideNavItem timeOff = new SideNavItem("My Time-off", MainView.class, LineAwesomeIcon.THEMEISLE.create());
SideNavItem timesheet = new SideNavItem("My Timesheet", MainView.class, LineAwesomeIcon.HOURGLASS_START_SOLID.create());
SideNavItem profile = new SideNavItem("My Profile", MainView.class, LineAwesomeIcon.USER_EDIT_SOLID.create());
nav.addItem(new SideNavItem("Home", MainView.class, LineAwesomeIcon.HOME_SOLID.create()));
nav.addItem(new SideNavItem("Assessments", AssessmentsListView.class, LineAwesomeIcon.RIBBON_SOLID.create()));
nav.addItem(new SideNavItem("Users", UsersListView.class, LineAwesomeIcon.USER.create()));
nav.addItem(new SideNavItem("Questions", QuestionsListView.class, LineAwesomeIcon.QUESTION_SOLID.create()));
nav.addItem(admin);
nav.addItem(recruiting);
nav.addItem(profile);
nav.addItem(timesheet);
nav.addItem(timeOff);
});
return nav;

View File

@ -56,8 +56,8 @@ public class QuestionView extends BeanValidationForm<Question> implements HasUrl
@Override
public void setParameter(final BeforeEvent beforeEvent, final String s) {
if (StringUtils.isNotBlank(s) && !"new".equals(s)) {
var user = questionService.getQuestion(UUID.fromString(s));
setEntityWithEnabledSave(user);
var question = questionService.getQuestion(UUID.fromString(s));
setEntityWithEnabledSave(question);
} else if ("new".equals(s)) {
setEntityWithEnabledSave(new Question());
} else {

View File

@ -221,7 +221,7 @@ public class SubmissionView extends Main implements HasUrlParameter<String> {
if (dl.getChildren().collect(Collectors.toList()).isEmpty()) {
dl.add(
createItem("Candidato:", assessment.getAppUser().getEmail()),
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"))

View File

@ -19,12 +19,13 @@ spring.mail.properties.mail.smtp.starttls.required=true
# spring.datasource.url=jdbc:h2:mem:testdb
# spring.datasource.url=jdbc:h2:file:./db
spring.datasource.url=jdbc:h2:file:/var/opt/pfs/db
# spring.datasource.url=jdbc:h2:file:/var/opt/pfs/db
spring.datasource.url=${DB_URL:jdbc:h2:mem:testdb}
spring.jpa.hibernate.ddl-auto=update
spring.sql.init.mode=${SQL_INIT:embedded}
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=${H2_PASS}
spring.datasource.password=${H2_PASS:sa}
spring.h2.console.settings.web-allow-others=true
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization = true

View File

@ -1,9 +1,9 @@
insert into app_user (id, version, email) values ('23471ab3-f639-4d2b-9541-7227f4ea7ee6', 1, 'foo@bar.com');
insert into candidate (id, version, email) values ('23471ab3-f639-4d2b-9541-7227f4ea7ee6', 1, 'foo@bar.com');
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 assessment(id, version, app_user_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 ASSESSMENT_QUESTIONS (assessment_id, question_id) values ('46b153f4-23fd-462f-8430-fbe67b83caab', 'a7e00ff8-da41-4624-b31c-1b13c3f2e3ae');