diff --git a/backend/src/main/java/ch/puzzle/pcts/Constants.java b/backend/src/main/java/ch/puzzle/pcts/Constants.java index c5b59fd90..d34d2ea6d 100644 --- a/backend/src/main/java/ch/puzzle/pcts/Constants.java +++ b/backend/src/main/java/ch/puzzle/pcts/Constants.java @@ -14,6 +14,7 @@ public class Constants { public static final String LEADERSHIP_EXPERIENCE_TYPE = "leadershipExperienceType"; public static final String CALCULATION = "calculation"; public static final String LEADERSHIP_EXPERIENCE = "leadershipExperience"; + public static final String MEMBER_OVERVIEW = "memberOverview"; private Constants() { } diff --git a/backend/src/main/java/ch/puzzle/pcts/controller/MemberOverviewController.java b/backend/src/main/java/ch/puzzle/pcts/controller/MemberOverviewController.java new file mode 100644 index 000000000..b343db886 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/controller/MemberOverviewController.java @@ -0,0 +1,41 @@ +package ch.puzzle.pcts.controller; + +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewDto; +import ch.puzzle.pcts.mapper.MemberOverviewMapper; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.service.business.MemberOverviewBusinessService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/v1/member-overviews") +@Tag(name = "member-overviews", description = "Get the member and everything associated with the member") +public class MemberOverviewController { + private final MemberOverviewMapper memberOverviewMapper; + private final MemberOverviewBusinessService memberOverviewBusinessService; + + public MemberOverviewController(MemberOverviewMapper mapper, MemberOverviewBusinessService service) { + this.memberOverviewMapper = mapper; + this.memberOverviewBusinessService = service; + } + + @Operation(summary = "Get the member-overview by memberId") + @ApiResponse(responseCode = "200", description = "Successfully retrieved the member-overview.", content = { + @Content(mediaType = "application/json", schema = @Schema(implementation = MemberOverviewDto.class)) }) + @GetMapping("{memberId}") + public ResponseEntity getMemberOverviewByMemberId(@Parameter(description = "The ID of the member whose overview should be retrieved.", required = true) + @PathVariable Long memberId) { + List memberOverviews = memberOverviewBusinessService.getById(memberId); + return ResponseEntity.ok(memberOverviewMapper.toDto(memberOverviews)); + } +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewCvDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewCvDto.java new file mode 100644 index 000000000..3540cd31c --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewCvDto.java @@ -0,0 +1,12 @@ +package ch.puzzle.pcts.dto.memberoverview; + +import ch.puzzle.pcts.dto.memberoverview.certificate.MemberOverviewCertificateDto; +import ch.puzzle.pcts.dto.memberoverview.degree.MemberOverviewDegreeDto; +import ch.puzzle.pcts.dto.memberoverview.experience.MemberOverviewExperienceDto; +import ch.puzzle.pcts.dto.memberoverview.leadershipexperience.MemberOverviewLeadershipExperienceDto; +import java.util.List; + +public record MemberOverviewCvDto(List degrees, List experiences, + List certificates, + List leadershipExperiences) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewDto.java new file mode 100644 index 000000000..603793a75 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewDto.java @@ -0,0 +1,4 @@ +package ch.puzzle.pcts.dto.memberoverview; + +public record MemberOverviewDto(MemberOverviewMemberDto member, MemberOverviewCvDto cv) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewMemberDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewMemberDto.java new file mode 100644 index 000000000..3ec4d7448 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/MemberOverviewMemberDto.java @@ -0,0 +1,16 @@ +package ch.puzzle.pcts.dto.memberoverview; + +import ch.puzzle.pcts.model.member.EmploymentState; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; + +public record MemberOverviewMemberDto( + @Schema(description = "The unique identifier of the member.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY) Long id, + @Schema(description = "The first name of member.", example = "Susi", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String firstName, + @Schema(description = "The last name of the member.", example = "Miller", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String lastName, + @Schema(description = "The employment state of the member.", example = "APPLICANT", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) EmploymentState employmentState, + @Schema(description = "The abbreviation of the member.", example = "SM", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String abbreviation, + @Schema(description = "The member's hire date.", example = "2025-09-24", requiredMode = Schema.RequiredMode.NOT_REQUIRED) LocalDate dateOfHire, + @Schema(description = "The member's birth date.", example = "1995-02-19", requiredMode = Schema.RequiredMode.REQUIRED) LocalDate birthDate, + @Schema(description = "The organisation unit name of the member.", example = "/dev", requiredMode = Schema.RequiredMode.NOT_REQUIRED) String organisationUnitName) { +} \ No newline at end of file diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/certificate/MemberOverviewCertificateDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/certificate/MemberOverviewCertificateDto.java new file mode 100644 index 000000000..16b2d5324 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/certificate/MemberOverviewCertificateDto.java @@ -0,0 +1,11 @@ +package ch.puzzle.pcts.dto.memberoverview.certificate; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; + +public record MemberOverviewCertificateDto( + @Schema(description = "The unique identifier of the member certificate.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, nullable = false) Long id, + @Schema(description = "The name of the certificate-type.", example = "Certified GitOps Associate", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String certificateTypeName, + @Schema(description = "The date when the member completed the certificate.", example = "2025-09-24", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) LocalDate completedAt, + @Schema(description = "An optional comment for the member certificate.", example = "Completed via fast-track program", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) String comment) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/degree/MemberOverviewDegreeDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/degree/MemberOverviewDegreeDto.java new file mode 100644 index 000000000..e35c02555 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/degree/MemberOverviewDegreeDto.java @@ -0,0 +1,12 @@ +package ch.puzzle.pcts.dto.memberoverview.degree; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; + +public record MemberOverviewDegreeDto( + @Schema(description = "The unique identifier of the degree.", example = "1", accessMode = Schema.AccessMode.READ_ONLY) Long id, + @Schema(description = "The name of the degree.", example = "Master of Computer Science", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String name, + @Schema(description = "The name of the degree-type.", example = "Bachelor of Science", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String degreeTypeName, + @Schema(description = "The start date of the degree program.", example = "2018-09-01", requiredMode = Schema.RequiredMode.REQUIRED) LocalDate startDate, + @Schema(description = "The end date of the degree program.", example = "2022-06-30") LocalDate endDate) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/experience/MemberOverviewExperienceDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/experience/MemberOverviewExperienceDto.java new file mode 100644 index 000000000..5820b70fc --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/experience/MemberOverviewExperienceDto.java @@ -0,0 +1,14 @@ +package ch.puzzle.pcts.dto.memberoverview.experience; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; + +public record MemberOverviewExperienceDto( + @Schema(description = "The unique identifier of the experience.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED, nullable = false, accessMode = Schema.AccessMode.READ_ONLY) Long id, + @Schema(description = "The name or title of the experience", example = "Software Engineer Intern", requiredMode = Schema.RequiredMode.REQUIRED, nullable = false, minLength = 1) String name, + @Schema(description = "The employer or organization where the experience took place.", example = "TechCorp Inc.", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) String employer, + @Schema(description = "The name of the experience-type.", example = "Management", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String experienceTypeName, + @Schema(description = "Additional comments about the experience.", example = "Worked on backend API development using Spring Boot.", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) String comment, + @Schema(description = "The start date of the experience.", example = "2021-06-01", requiredMode = Schema.RequiredMode.REQUIRED, nullable = false) LocalDate startDate, + @Schema(description = "The end date of the experience.", example = "2021-12-31", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) LocalDate endDate) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceDto.java new file mode 100644 index 000000000..58985a179 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceDto.java @@ -0,0 +1,11 @@ +package ch.puzzle.pcts.dto.memberoverview.leadershipexperience; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record MemberOverviewLeadershipExperienceDto( + @Schema(description = "The unique identifier of the leadership experience.", example = "1", requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, nullable = false) Long id, + + @Schema(description = "The type of leadership experience awarded to the member.", exampleClasses = MemberOverviewLeadershipExperienceTypeDto.class, requiredMode = Schema.RequiredMode.REQUIRED, nullable = false) MemberOverviewLeadershipExperienceTypeDto experience, + + @Schema(description = "An optional comment for the leadership experience.", example = "Completed via fast-track program", requiredMode = Schema.RequiredMode.NOT_REQUIRED, nullable = true) String comment) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceTypeDto.java b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceTypeDto.java new file mode 100644 index 000000000..2743b09d4 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/dto/memberoverview/leadershipexperience/MemberOverviewLeadershipExperienceTypeDto.java @@ -0,0 +1,9 @@ +package ch.puzzle.pcts.dto.memberoverview.leadershipexperience; + +import ch.puzzle.pcts.model.certificatetype.CertificateKind; +import io.swagger.v3.oas.annotations.media.Schema; + +public record MemberOverviewLeadershipExperienceTypeDto( + @Schema(description = "The name of the leadership-experience-type.", example = "", requiredMode = Schema.RequiredMode.REQUIRED, minLength = 1) String name, + @Schema(description = "The kind of leadership-experience-type which is either MILITARY_FUNCTION, YOUTH_AND_SPORT or LEADERSHIP_TRAINING", example = "LEADERSHIP_TRAINING", requiredMode = Schema.RequiredMode.REQUIRED) CertificateKind experienceKind) { +} diff --git a/backend/src/main/java/ch/puzzle/pcts/mapper/MemberOverviewMapper.java b/backend/src/main/java/ch/puzzle/pcts/mapper/MemberOverviewMapper.java new file mode 100644 index 000000000..91ca35ab9 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/mapper/MemberOverviewMapper.java @@ -0,0 +1,95 @@ +package ch.puzzle.pcts.mapper; + +import ch.puzzle.pcts.dto.memberoverview.*; +import ch.puzzle.pcts.dto.memberoverview.certificate.MemberOverviewCertificateDto; +import ch.puzzle.pcts.dto.memberoverview.degree.MemberOverviewDegreeDto; +import ch.puzzle.pcts.dto.memberoverview.experience.MemberOverviewExperienceDto; +import ch.puzzle.pcts.dto.memberoverview.leadershipexperience.MemberOverviewLeadershipExperienceDto; +import ch.puzzle.pcts.dto.memberoverview.leadershipexperience.MemberOverviewLeadershipExperienceTypeDto; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.springframework.stereotype.Component; + +@Component +public class MemberOverviewMapper { + + public MemberOverviewDto toDto(List memberOverviews) { + if (memberOverviews == null || memberOverviews.isEmpty()) { + return null; + } + + Map degreeMap = new HashMap<>(); + Map experienceMap = new HashMap<>(); + Map certificateMap = new HashMap<>(); + Map leadershipMap = new HashMap<>(); + + for (MemberOverview row : memberOverviews) { + + if (row.getDegreeId() != null) { + degreeMap.putIfAbsent(row.getDegreeId(), mapToDegree(row)); + } + + if (row.getExperienceId() != null) { + experienceMap.putIfAbsent(row.getExperienceId(), mapToExperience(row)); + } + + if (row.getCertificateId() != null) { + if (row.getLeadershipTypeKind().isLeadershipExperienceType()) { + leadershipMap.putIfAbsent(row.getCertificateId(), mapToLeadershipExperience(row)); + } else { + certificateMap.putIfAbsent(row.getCertificateId(), mapToCertificate(row)); + } + } + } + + MemberOverviewCvDto cvDto = new MemberOverviewCvDto(List.copyOf(degreeMap.values()), + List.copyOf(experienceMap.values()), + List.copyOf(certificateMap.values()), + List.copyOf(leadershipMap.values())); + + return new MemberOverviewDto(getMemberDto(memberOverviews), cvDto); + } + + private MemberOverviewMemberDto getMemberDto(List memberOverviews) { + MemberOverview first = memberOverviews.getFirst(); + return new MemberOverviewMemberDto(first.getMemberId(), + first.getFirstName(), + first.getLastName(), + first.getEmploymentState(), + first.getAbbreviation(), + first.getDateOfHire(), + first.getBirthDate(), + first.getOrganisationUnitName()); + } + + private MemberOverviewDegreeDto mapToDegree(MemberOverview e) { + return new MemberOverviewDegreeDto(e + .getDegreeId(), e.getDegreeName(), e.getDegreeTypeName(), e.getDegreeStartDate(), e.getDegreeEndDate()); + } + + private MemberOverviewExperienceDto mapToExperience(MemberOverview e) { + return new MemberOverviewExperienceDto(e.getExperienceId(), + e.getExperienceName(), + e.getExperienceEmployer(), + e.getExperienceTypeName(), + e.getExperienceComment(), + e.getExperienceStartDate(), + e.getExperienceEndDate()); + } + + private MemberOverviewCertificateDto mapToCertificate(MemberOverview e) { + return new MemberOverviewCertificateDto(e.getCertificateId(), + e.getCertificateTypeName(), + e.getCertificateCompletedAt(), + e.getCertificateComment()); + } + + private MemberOverviewLeadershipExperienceDto mapToLeadershipExperience(MemberOverview e) { + return new MemberOverviewLeadershipExperienceDto(e.getCertificateId(), + new MemberOverviewLeadershipExperienceTypeDto(e + .getCertificateTypeName(), e.getLeadershipTypeKind()), + e.getCertificateComment()); + } +} \ No newline at end of file diff --git a/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverview.java b/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverview.java new file mode 100644 index 000000000..17a625be5 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverview.java @@ -0,0 +1,536 @@ +package ch.puzzle.pcts.model.memberoverview; + +import static org.apache.commons.lang3.StringUtils.trim; + +import ch.puzzle.pcts.model.certificatetype.CertificateKind; +import ch.puzzle.pcts.model.member.EmploymentState; +import jakarta.persistence.*; +import java.time.LocalDate; +import java.util.Objects; + +@Entity +@Table(name = "member_overview") +@IdClass(MemberOverviewId.class) +public class MemberOverview { + + @Id + private Long memberId; + private String firstName; + private String lastName; + private String abbreviation; + + private LocalDate birthDate; + private LocalDate dateOfHire; + @Enumerated(EnumType.STRING) + private EmploymentState employmentState; + private String organisationUnitName; + + @Id + private Long certificateId; + private LocalDate certificateCompletedAt; + private String certificateComment; + + private String certificateTypeName; + @Enumerated(EnumType.STRING) + private CertificateKind leadershipTypeKind; + + @Id + private Long degreeId; + private String degreeName; + private LocalDate degreeStartDate; + private LocalDate degreeEndDate; + + private String degreeTypeName; + + @Id + private Long experienceId; + private String experienceName; + private String experienceEmployer; + private LocalDate experienceStartDate; + private LocalDate experienceEndDate; + private String experienceComment; + + private String experienceTypeName; + + private MemberOverview(Builder builder) { + memberId = builder.memberId; + firstName = trim(builder.firstName); + lastName = trim(builder.lastName); + abbreviation = trim(builder.abbreviation); + birthDate = builder.birthDate; + dateOfHire = builder.dateOfHire; + employmentState = builder.employmentState; + organisationUnitName = trim(builder.organisationUnitName); + certificateId = builder.certificateId; + certificateCompletedAt = builder.certificateCompletedAt; + certificateComment = trim(builder.certificateComment); + certificateTypeName = trim(builder.certificateTypeName); + degreeId = builder.degreeId; + degreeName = trim(builder.degreeName); + degreeStartDate = builder.degreeStartDate; + degreeEndDate = builder.degreeEndDate; + leadershipTypeKind = builder.leadershipTypeKind; + degreeTypeName = trim(builder.degreeTypeName); + experienceId = builder.experienceId; + experienceName = trim(builder.experienceName); + experienceEmployer = trim(builder.experienceEmployer); + experienceStartDate = builder.experienceStartDate; + experienceEndDate = builder.experienceEndDate; + experienceComment = trim(builder.experienceComment); + experienceTypeName = trim(builder.experienceTypeName); + } + + public MemberOverview() { + + } + + public Long getMemberId() { + return memberId; + } + + public void setMemberId(Long memberId) { + this.memberId = memberId; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = trim(firstName); + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = trim(lastName); + } + + public String getAbbreviation() { + return abbreviation; + } + + public void setAbbreviation(String abbreviation) { + this.abbreviation = trim(abbreviation); + } + + public LocalDate getBirthDate() { + return birthDate; + } + + public void setBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + } + + public LocalDate getDateOfHire() { + return dateOfHire; + } + + public void setDateOfHire(LocalDate dateOfHire) { + this.dateOfHire = dateOfHire; + } + + public EmploymentState getEmploymentState() { + return employmentState; + } + + public void setEmploymentState(EmploymentState employmentState) { + this.employmentState = employmentState; + } + + public String getOrganisationUnitName() { + return organisationUnitName; + } + + public void setOrganisationUnitName(String organisationUnitName) { + this.organisationUnitName = trim(organisationUnitName); + } + + public Long getCertificateId() { + return certificateId; + } + + public void setCertificateId(Long certificateId) { + this.certificateId = certificateId; + } + + public LocalDate getCertificateCompletedAt() { + return certificateCompletedAt; + } + + public void setCertificateCompletedAt(LocalDate certificateCompletedAt) { + this.certificateCompletedAt = certificateCompletedAt; + } + + public String getCertificateComment() { + return certificateComment; + } + + public void setCertificateComment(String certificateComment) { + this.certificateComment = trim(certificateComment); + } + + public String getCertificateTypeName() { + return certificateTypeName; + } + + public void setCertificateTypeName(String certificateTypeName) { + this.certificateTypeName = trim(certificateTypeName); + } + + public Long getDegreeId() { + return degreeId; + } + + public void setDegreeId(Long degreeId) { + this.degreeId = degreeId; + } + + public String getDegreeName() { + return degreeName; + } + + public void setDegreeName(String degreeName) { + this.degreeName = trim(degreeName); + } + + public LocalDate getDegreeStartDate() { + return degreeStartDate; + } + + public void setDegreeStartDate(LocalDate degreeStartDate) { + this.degreeStartDate = degreeStartDate; + } + + public LocalDate getDegreeEndDate() { + return degreeEndDate; + } + + public void setDegreeEndDate(LocalDate degreeEndDate) { + this.degreeEndDate = degreeEndDate; + } + + public CertificateKind getLeadershipTypeKind() { + return leadershipTypeKind; + } + + public void setLeadershipTypeKind(CertificateKind leadershipTypeKind) { + this.leadershipTypeKind = leadershipTypeKind; + } + + public String getDegreeTypeName() { + return degreeTypeName; + } + + public void setDegreeTypeName(String degreeTypeName) { + this.degreeTypeName = trim(degreeTypeName); + } + + public Long getExperienceId() { + return experienceId; + } + + public void setExperienceId(Long experienceId) { + this.experienceId = experienceId; + } + + public String getExperienceName() { + return experienceName; + } + + public void setExperienceName(String experienceName) { + this.experienceName = trim(experienceName); + } + + public String getExperienceEmployer() { + return experienceEmployer; + } + + public void setExperienceEmployer(String experienceEmployer) { + this.experienceEmployer = trim(experienceEmployer); + } + + public LocalDate getExperienceStartDate() { + return experienceStartDate; + } + + public void setExperienceStartDate(LocalDate experienceStartDate) { + this.experienceStartDate = experienceStartDate; + } + + public LocalDate getExperienceEndDate() { + return experienceEndDate; + } + + public void setExperienceEndDate(LocalDate experienceEndDate) { + this.experienceEndDate = experienceEndDate; + } + + public String getExperienceComment() { + return experienceComment; + } + + public void setExperienceComment(String experienceComment) { + this.experienceComment = trim(experienceComment); + } + + public String getExperienceTypeName() { + return experienceTypeName; + } + + public void setExperienceTypeName(String experienceTypeName) { + this.experienceTypeName = trim(experienceTypeName); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MemberOverview that)) { + return false; + } + return Objects.equals(getMemberId(), that.getMemberId()) && Objects.equals(getFirstName(), that.getFirstName()) + && Objects.equals(getLastName(), that.getLastName()) + && Objects.equals(getAbbreviation(), that.getAbbreviation()) + && Objects.equals(getBirthDate(), that.getBirthDate()) + && Objects.equals(getDateOfHire(), that.getDateOfHire()) + && getEmploymentState() == that.getEmploymentState() + && Objects.equals(getOrganisationUnitName(), that.getOrganisationUnitName()) + && Objects.equals(getCertificateId(), that.getCertificateId()) + && Objects.equals(getCertificateCompletedAt(), that.getCertificateCompletedAt()) + && Objects.equals(getCertificateComment(), that.getCertificateComment()) + && Objects.equals(getCertificateTypeName(), that.getCertificateTypeName()) + && getLeadershipTypeKind() == that.getLeadershipTypeKind() + && Objects.equals(getDegreeId(), that.getDegreeId()) + && Objects.equals(getDegreeName(), that.getDegreeName()) + && Objects.equals(getDegreeStartDate(), that.getDegreeStartDate()) + && Objects.equals(getDegreeEndDate(), that.getDegreeEndDate()) + && Objects.equals(getDegreeTypeName(), that.getDegreeTypeName()) + && Objects.equals(getExperienceId(), that.getExperienceId()) + && Objects.equals(getExperienceName(), that.getExperienceName()) + && Objects.equals(getExperienceEmployer(), that.getExperienceEmployer()) + && Objects.equals(getExperienceStartDate(), that.getExperienceStartDate()) + && Objects.equals(getExperienceEndDate(), that.getExperienceEndDate()) + && Objects.equals(getExperienceComment(), that.getExperienceComment()) + && Objects.equals(getExperienceTypeName(), that.getExperienceTypeName()); + } + + @Override + public int hashCode() { + return Objects + .hash(getMemberId(), + getFirstName(), + getLastName(), + getAbbreviation(), + getBirthDate(), + getDateOfHire(), + getEmploymentState(), + getOrganisationUnitName(), + getCertificateId(), + getCertificateCompletedAt(), + getCertificateComment(), + getCertificateTypeName(), + getLeadershipTypeKind(), + getDegreeId(), + getDegreeName(), + getDegreeStartDate(), + getDegreeEndDate(), + getDegreeTypeName(), + getExperienceId(), + getExperienceName(), + getExperienceEmployer(), + getExperienceStartDate(), + getExperienceEndDate(), + getExperienceComment(), + getExperienceTypeName()); + } + + @Override + public String toString() { + return "MemberOverview{" + "memberId=" + getMemberId() + ", firstName='" + getFirstName() + '\'' + + ", lastName='" + getLastName() + '\'' + ", abbreviation='" + getAbbreviation() + '\'' + ", birthDate=" + + getBirthDate() + ", dateOfHire=" + getDateOfHire() + ", employmentState=" + getEmploymentState() + + ", organisationUnitName='" + getOrganisationUnitName() + '\'' + ", certificateId=" + getCertificateId() + + ", certificateCompletedAt=" + getCertificateCompletedAt() + ", certificateComment='" + + getCertificateComment() + '\'' + ", certificateTypeName='" + getCertificateTypeName() + '\'' + + ", leadershipTypeKind=" + getLeadershipTypeKind() + ", degreeId=" + getDegreeId() + ", degreeName='" + + getDegreeName() + '\'' + ", degreeStartDate=" + getDegreeStartDate() + ", degreeEndDate=" + + getDegreeEndDate() + ", degreeTypeName='" + getDegreeTypeName() + '\'' + ", experienceId=" + + getExperienceId() + ", experienceName='" + getExperienceName() + '\'' + ", experienceEmployer='" + + getExperienceEmployer() + '\'' + ", experienceStartDate=" + getExperienceStartDate() + + ", experienceEndDate=" + getExperienceEndDate() + ", experienceComment='" + getExperienceComment() + + '\'' + ", experienceTypeName='" + getExperienceTypeName() + '\'' + '}'; + } + + public static final class Builder { + private Long memberId; + private String firstName; + private String lastName; + private String abbreviation; + private LocalDate birthDate; + private LocalDate dateOfHire; + private EmploymentState employmentState; + + private String organisationUnitName; + + private Long certificateId; + private LocalDate certificateCompletedAt; + private String certificateComment; + + private String certificateTypeName; + + private Long degreeId; + private String degreeName; + private LocalDate degreeStartDate; + private LocalDate degreeEndDate; + + private CertificateKind leadershipTypeKind; + + private String degreeTypeName; + + private Long experienceId; + private String experienceName; + private String experienceEmployer; + private LocalDate experienceStartDate; + private LocalDate experienceEndDate; + private String experienceComment; + + private String experienceTypeName; + + private Builder() { + } + + public static Builder builder() { + return new Builder(); + } + + public Builder withMemberId(Long memberId) { + this.memberId = memberId; + return this; + } + + public Builder withFirstName(String firstName) { + this.firstName = trim(firstName); + return this; + } + + public Builder withLastName(String lastName) { + this.lastName = trim(lastName); + return this; + } + + public Builder withAbbreviation(String abbreviation) { + this.abbreviation = trim(abbreviation); + return this; + } + + public Builder withBirthDate(LocalDate birthDate) { + this.birthDate = birthDate; + return this; + } + + public Builder withDateOfHire(LocalDate dateOfHire) { + this.dateOfHire = dateOfHire; + return this; + } + + public Builder withEmploymentState(EmploymentState employmentState) { + this.employmentState = employmentState; + return this; + } + + public Builder withOrganisationUnitName(String organisationUnitName) { + this.organisationUnitName = trim(organisationUnitName); + return this; + } + + public Builder withCertificateId(Long certificateId) { + this.certificateId = certificateId; + return this; + } + + public Builder withCertificateCompletedAt(LocalDate certificateCompletedAt) { + this.certificateCompletedAt = certificateCompletedAt; + return this; + } + + public Builder withCertificateComment(String certificateComment) { + this.certificateComment = trim(certificateComment); + return this; + } + + public Builder withCertificateTypeName(String certificateTypeName) { + this.certificateTypeName = trim(certificateTypeName); + return this; + } + + public Builder withleadershipTypeKind(CertificateKind leadershipTypeKind) { + this.leadershipTypeKind = leadershipTypeKind; + return this; + } + + public Builder withDegreeId(Long degreeId) { + this.degreeId = degreeId; + return this; + } + + public Builder withDegreeName(String degreeName) { + this.degreeName = trim(degreeName); + return this; + } + + public Builder withDegreeStartDate(LocalDate degreeStartDate) { + this.degreeStartDate = degreeStartDate; + return this; + } + + public Builder withDegreeEndDate(LocalDate degreeEndDate) { + this.degreeEndDate = degreeEndDate; + return this; + } + + public Builder withDegreeTypeName(String degreeTypeName) { + this.degreeTypeName = trim(degreeTypeName); + return this; + } + + public Builder withExperienceId(Long experienceId) { + this.experienceId = experienceId; + return this; + } + + public Builder withExperienceName(String experienceName) { + this.experienceName = trim(experienceName); + return this; + } + + public Builder withExperienceEmployer(String experienceEmployer) { + this.experienceEmployer = trim(experienceEmployer); + return this; + } + + public Builder withExperienceStartDate(LocalDate experienceStartDate) { + this.experienceStartDate = experienceStartDate; + return this; + } + + public Builder withExperienceEndDate(LocalDate experienceEndDate) { + this.experienceEndDate = experienceEndDate; + return this; + } + + public Builder withExperienceComment(String experienceComment) { + this.experienceComment = trim(experienceComment); + return this; + } + + public Builder withExperienceTypeName(String experienceTypeName) { + this.experienceTypeName = trim(experienceTypeName); + return this; + } + + public MemberOverview build() { + return new MemberOverview(this); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverviewId.java b/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverviewId.java new file mode 100644 index 000000000..9a2155f5e --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/model/memberoverview/MemberOverviewId.java @@ -0,0 +1,76 @@ +package ch.puzzle.pcts.model.memberoverview; + +import java.io.Serializable; +import java.util.Objects; + +public class MemberOverviewId implements Serializable { + + private Long memberId; + private Long certificateId; + private Long degreeId; + private Long experienceId; + + public MemberOverviewId() { + } + + public MemberOverviewId(Long memberId, Long certificateId, Long degreeId, Long experienceId) { + this.memberId = memberId; + this.certificateId = certificateId; + this.degreeId = degreeId; + this.experienceId = experienceId; + } + + public Long getMemberId() { + return memberId; + } + + public void setMemberId(Long memberId) { + this.memberId = memberId; + } + + public Long getCertificateId() { + return certificateId; + } + + public void setCertificateId(Long certificateId) { + this.certificateId = certificateId; + } + + public Long getDegreeId() { + return degreeId; + } + + public void setDegreeId(Long degreeId) { + this.degreeId = degreeId; + } + + public Long getExperienceId() { + return experienceId; + } + + public void setExperienceId(Long experienceId) { + this.experienceId = experienceId; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MemberOverviewId that)) { + return false; + } + return Objects.equals(getMemberId(), that.getMemberId()) + && Objects.equals(getCertificateId(), that.getCertificateId()) + && Objects.equals(getDegreeId(), that.getDegreeId()) + && Objects.equals(getExperienceId(), that.getExperienceId()); + } + + @Override + public int hashCode() { + return Objects.hash(getMemberId(), getCertificateId(), getDegreeId(), getExperienceId()); + } + + @Override + public String toString() { + return "MemberOverviewId{" + "memberId=" + memberId + ", certificateId=" + certificateId + ", degreeId=" + + degreeId + ", experienceId=" + experienceId + '}'; + } +} diff --git a/backend/src/main/java/ch/puzzle/pcts/repository/MemberOverviewRepository.java b/backend/src/main/java/ch/puzzle/pcts/repository/MemberOverviewRepository.java new file mode 100644 index 000000000..d22bf03e2 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/repository/MemberOverviewRepository.java @@ -0,0 +1,11 @@ +package ch.puzzle.pcts.repository; + +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberOverviewRepository extends JpaRepository { + List findAllByMemberId(Long id); +} diff --git a/backend/src/main/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessService.java b/backend/src/main/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessService.java new file mode 100644 index 000000000..d63faab97 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessService.java @@ -0,0 +1,27 @@ +package ch.puzzle.pcts.service.business; + +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.service.persistence.MemberOverviewPersistenceService; +import ch.puzzle.pcts.service.validation.MemberOverviewValidationService; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class MemberOverviewBusinessService { + + private final MemberOverviewPersistenceService persistenceService; + + private final MemberOverviewValidationService validationService; + + public MemberOverviewBusinessService(MemberOverviewPersistenceService persistenceService, + MemberOverviewValidationService validationService) { + this.persistenceService = persistenceService; + this.validationService = validationService; + } + + public List getById(Long id) { + validationService.validateOnGetById(id); + + return persistenceService.getById(id); + } +} diff --git a/backend/src/main/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceService.java b/backend/src/main/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceService.java new file mode 100644 index 000000000..2666429bb --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceService.java @@ -0,0 +1,47 @@ +package ch.puzzle.pcts.service.persistence; + +import static ch.puzzle.pcts.Constants.MEMBER_OVERVIEW; + +import ch.puzzle.pcts.dto.error.ErrorKey; +import ch.puzzle.pcts.dto.error.FieldKey; +import ch.puzzle.pcts.dto.error.GenericErrorDto; +import ch.puzzle.pcts.exception.PCTSException; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.repository.MemberOverviewRepository; +import java.util.List; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +@Service +public class MemberOverviewPersistenceService { + + private final MemberOverviewRepository repository; + + public MemberOverviewPersistenceService(MemberOverviewRepository repository) { + this.repository = repository; + } + + public List getById(Long id) { + List result = repository.findAllByMemberId(id); + + if (result.isEmpty()) { + throw new PCTSException(HttpStatus.NOT_FOUND, + List + .of(new GenericErrorDto(ErrorKey.NOT_FOUND, + Map + .of(FieldKey.ENTITY, + entityName(), + FieldKey.FIELD, + "id", + FieldKey.IS, + id.toString())))); + } + + return result; + } + + protected String entityName() { + return MEMBER_OVERVIEW; + } +} diff --git a/backend/src/main/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationService.java b/backend/src/main/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationService.java new file mode 100644 index 000000000..ede852722 --- /dev/null +++ b/backend/src/main/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationService.java @@ -0,0 +1,22 @@ +package ch.puzzle.pcts.service.validation; + +import static ch.puzzle.pcts.service.validation.ValidationBase.buildGenericErrorDto; + +import ch.puzzle.pcts.dto.error.ErrorKey; +import ch.puzzle.pcts.dto.error.FieldKey; +import ch.puzzle.pcts.exception.PCTSException; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +// Does not inherit from ValidationBase due to the model not extending from the base either, which is due to the primary key being a composite key +@Service +public class MemberOverviewValidationService { + + public void validateOnGetById(Long id) { + if (id == null) { + throw new PCTSException(HttpStatus.BAD_REQUEST, + buildGenericErrorDto(ErrorKey.VALIDATION, Map.of(FieldKey.FIELD, "id"))); + } + } +} diff --git a/backend/src/main/resources/db/migration/V0_0_14__add_member_view.sql b/backend/src/main/resources/db/migration/V0_0_14__add_member_view.sql new file mode 100644 index 000000000..68efa641c --- /dev/null +++ b/backend/src/main/resources/db/migration/V0_0_14__add_member_view.sql @@ -0,0 +1,57 @@ +DROP VIEW IF EXISTS member_overview; + +CREATE VIEW member_overview AS +SELECT + + m.id AS member_id, + m.first_name, + m.last_name, + m.abbreviation, + m.employment_state, + m.date_of_hire, + m.birth_date, + + ou.name AS organisation_unit_name, + + COALESCE(c.id, 0) AS certificate_id, + c.completed_at AS certificate_completed_at, + c.valid_until AS certificate_valid_until, + c.comment AS certificate_comment, + + ct.name AS certificate_type_name, + ct.comment AS certificate_type_comment, + ct.certificate_kind AS leadership_type_kind, + + COALESCE(d.id, 0) AS degree_id, + d.name AS degree_name, + d.start_date AS degree_start_date, + d.end_date AS degree_end_date, + d.comment AS degree_comment, + dt.name AS degree_type_name, + + COALESCE(e.id, 0) AS experience_id, + e.name AS experience_name, + e.employer AS experience_employer, + e.start_date AS experience_start_date, + e.end_date AS experience_end_date, + e.comment AS experience_comment, + et.name AS experience_type_name + +FROM member m + LEFT JOIN organisation_unit ou + ON ou.id = m.organisation_unit + + LEFT JOIN certificate c + ON c.member_id = m.id + LEFT JOIN certificate_type ct + ON ct.id = c.certificate_type_id + + LEFT JOIN degree d + ON d.member_id = m.id + LEFT JOIN degree_type dt + ON dt.id = d.degree_type_id + + LEFT JOIN experience e + ON e.member_id = m.id + LEFT JOIN experience_type et + ON et.id = e.experience_type_id; diff --git a/backend/src/test/java/ch/puzzle/pcts/architecture/ArchitectureTest.java b/backend/src/test/java/ch/puzzle/pcts/architecture/ArchitectureTest.java index d4f52f03b..d6de5601d 100644 --- a/backend/src/test/java/ch/puzzle/pcts/architecture/ArchitectureTest.java +++ b/backend/src/test/java/ch/puzzle/pcts/architecture/ArchitectureTest.java @@ -11,8 +11,7 @@ import static ch.puzzle.pcts.architecture.condition.ClassConditions.overrideToStringMethod; import static ch.puzzle.pcts.architecture.condition.CodeUnitConditions.trimAssignedStringFields; import static com.tngtech.archunit.base.DescribedPredicate.not; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage; -import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*; import static com.tngtech.archunit.lang.conditions.ArchConditions.and; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.*; import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes; @@ -268,9 +267,9 @@ void dtoShouldBeRecord() { rule.check(importedClasses); } - @DisplayName("Models should implement interface, have @Entity annotation and override methods") + @DisplayName("Models should override methods") @Test - void modelShouldImplementInterfaceAndOverrideMethods() { + void modelShouldOverrideMethods() { JavaClasses importedClasses = getMainSourceClasses(); ArchRule rule = classes() @@ -281,17 +280,54 @@ void modelShouldImplementInterfaceAndOverrideMethods() { .areNotEnums() .and() .areNotNestedClasses() - .should() - .beAnnotatedWith(Entity.class) - .andShould() - .implement(Model.class) - .andShould(overrideEqualsMethod) + .should(overrideEqualsMethod) .andShould(overrideHashCodeMethod) .andShould(overrideToStringMethod); rule.check(importedClasses); } + @DisplayName("Models should have @Entity annotation") + @Test + void modelShouldHaveEntityAnnotation() { + JavaClasses importedClasses = getMainSourceClasses(); + + ArchRule rule = classes() + .that(resideInAPackage("ch.puzzle.pcts.model..")) + .and() + .doNotHaveSimpleName("MemberOverviewId") + .and() + .areNotInterfaces() + .and() + .areNotEnums() + .and() + .areNotNestedClasses() + .should() + .beAnnotatedWith(Entity.class); + + rule.check(importedClasses); + } + + @DisplayName("Models should implement interface") + @Test + void modelShouldImplementInterface() { + JavaClasses importedClasses = getMainSourceClasses(); + + ArchRule rule = classes() + .that(resideInAPackage("ch.puzzle.pcts.model..")) + .and(not(resideInAnyPackage("ch.puzzle.pcts.model.memberoverview.."))) + .and() + .areNotInterfaces() + .and() + .areNotEnums() + .and() + .areNotNestedClasses() + .should() + .implement(Model.class); + + rule.check(importedClasses); + } + @DisplayName("Classes should reside in the correct packages based on naming conventions") @ParameterizedTest @ValueSource(strings = { "controller", "service", "mapper", "repository", "dto", "exception" }) diff --git a/backend/src/test/java/ch/puzzle/pcts/controller/MemberOverviewControllerIT.java b/backend/src/test/java/ch/puzzle/pcts/controller/MemberOverviewControllerIT.java new file mode 100644 index 000000000..c77ecaaa3 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/controller/MemberOverviewControllerIT.java @@ -0,0 +1,104 @@ +package ch.puzzle.pcts.controller; + +import static ch.puzzle.pcts.util.TestData.MEMBER_1_OVERVIEWS; +import static ch.puzzle.pcts.util.TestData.MEMBER_1_OVERVIEW_DTO; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import ch.puzzle.pcts.SpringSecurityConfig; +import ch.puzzle.pcts.dto.error.ErrorKey; +import ch.puzzle.pcts.dto.error.FieldKey; +import ch.puzzle.pcts.dto.error.GenericErrorDto; +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewDto; +import ch.puzzle.pcts.exception.PCTSException; +import ch.puzzle.pcts.mapper.MemberOverviewMapper; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.service.business.MemberOverviewBusinessService; +import ch.puzzle.pcts.util.JsonDtoMatcher; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.BDDMockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +@Import(SpringSecurityConfig.class) +@ExtendWith(MockitoExtension.class) +@WebMvcTest(MemberOverviewController.class) +class MemberOverviewControllerIT { + + @MockitoBean + private MemberOverviewBusinessService service; + + @MockitoBean + private MemberOverviewMapper mapper; + + @Autowired + private MockMvc mvc; + + private static final String BASEURL = "/api/v1/member-overviews"; + + private final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaTimeModule()) + .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + @DisplayName("Should successfully get member overview by member id") + @Test + void shouldGetMemberOverviewById() throws Exception { + Long memberId = 1L; + + List memberOverviews = MEMBER_1_OVERVIEWS; + MemberOverviewDto expectedDto = MEMBER_1_OVERVIEW_DTO; + + BDDMockito.given(service.getById(anyLong())).willReturn(memberOverviews); + BDDMockito.given(mapper.toDto(memberOverviews)).willReturn(expectedDto); + + mvc + .perform(get(BASEURL + "/" + memberId).with(csrf()).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(JsonDtoMatcher.matchesDto(expectedDto, "$")); + + verify(service, times(1)).getById(memberId); + verify(mapper, times(1)).toDto(memberOverviews); + } + + @DisplayName("Should return 404 with NOT_FOUND error when member does not exist") + @Test + void shouldReturn404WhenMemberNotFound() throws Exception { + Long memberId = 99L; + + Map attributes = Map + .of(FieldKey.ENTITY, "Member", FieldKey.FIELD, "id", FieldKey.IS, memberId.toString()); + + GenericErrorDto error = new GenericErrorDto(ErrorKey.NOT_FOUND, attributes); + + BDDMockito.given(service.getById(memberId)).willThrow(new PCTSException(HttpStatus.NOT_FOUND, List.of(error))); + + mvc + .perform(get(BASEURL + "/" + memberId).with(csrf()).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNotFound()) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$[0].key").value("NOT_FOUND")) + .andExpect(jsonPath("$[0].values.ENTITY").value("Member")) + .andExpect(jsonPath("$[0].values.FIELD").value("id")) + .andExpect(jsonPath("$[0].values.IS").value("99")); + + verify(service).getById(memberId); + verifyNoInteractions(mapper); + } +} diff --git a/backend/src/test/java/ch/puzzle/pcts/mapper/MemberOverviewMapperTest.java b/backend/src/test/java/ch/puzzle/pcts/mapper/MemberOverviewMapperTest.java new file mode 100644 index 000000000..df5a507e0 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/mapper/MemberOverviewMapperTest.java @@ -0,0 +1,55 @@ +package ch.puzzle.pcts.mapper; + +import static ch.puzzle.pcts.util.TestData.*; +import static org.assertj.core.api.Assertions.assertThat; + +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewDto; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MemberOverviewMapperTest { + + @InjectMocks + private MemberOverviewMapper mapper; + + @DisplayName("Should map valid MemberOverview lists to MemberOverviewDto") + @ParameterizedTest(name = "{index} => {0}") + @MethodSource("provideMemberOverviews") + void shouldReturnMemberOverviewDto(String description, List inputOverviews, + MemberOverviewDto expectedDto) { + MemberOverviewDto actualDto = mapper.toDto(inputOverviews); + + assertThat(actualDto).isNotNull(); + assertThat(actualDto).usingRecursiveComparison().ignoringCollectionOrder().isEqualTo(expectedDto); + } + + @Test + @DisplayName("Should return null when input list is null") + void shouldReturnNullWhenInputIsNull() { + assertThat(mapper.toDto(null)).isNull(); + } + + @Test + @DisplayName("Should return null when input list is empty") + void shouldReturnNullWhenInputIsEmpty() { + assertThat(mapper.toDto(Collections.emptyList())).isNull(); + } + + private static Stream provideMemberOverviews() { + return Stream + .of(Arguments.of("Standard Member (Full CV)", MEMBER_1_OVERVIEWS, MEMBER_1_OVERVIEW_DTO), + Arguments.of("Member with multiple same-type items", MEMBER_2_OVERVIEWS, MEMBER_2_OVERVIEW_DTO), + Arguments.of("Member with NO CV data (Sparse)", MEMBER_EMPTY_CV_OVERVIEWS, MEMBER_EMPTY_CV_DTO)); + } +} \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessServiceTest.java new file mode 100644 index 000000000..7de4e38ef --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/service/business/MemberOverviewBusinessServiceTest.java @@ -0,0 +1,45 @@ +package ch.puzzle.pcts.service.business; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.service.persistence.MemberOverviewPersistenceService; +import ch.puzzle.pcts.service.validation.MemberOverviewValidationService; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MemberOverviewBusinessServiceTest { + + @Mock + private MemberOverviewValidationService validationService; + + @Mock + private MemberOverviewPersistenceService persistenceService; + + @Mock + private List memberOverviews; + + @InjectMocks + private MemberOverviewBusinessService businessService; + + @DisplayName("Should get organisationUnit by id") + @Test + void shouldGetById() { + Long id = 1L; + when(persistenceService.getById(id)).thenReturn(memberOverviews); + + List result = businessService.getById(id); + + assertEquals(memberOverviews, result); + verify(persistenceService).getById(id); + verify(validationService).validateOnGetById(id); + } +} diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CalculationPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CalculationPersistenceServiceIT.java index 4d8b72947..871a86d53 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CalculationPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CalculationPersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.CALCULATIONS; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -43,25 +44,7 @@ Calculation getModel() { @Override List getAll() { - return List - .of(new Calculation(1L, - memberPersistenceServiceIT.getAll().getFirst(), - rolePersistenceServiceIT.getAll().getLast(), - CalculationState.DRAFT, - LocalDate.of(2025, 1, 14), - "Ldap User"), - new Calculation(2L, - memberPersistenceServiceIT.getAll().getLast(), - rolePersistenceServiceIT.getAll().getLast(), - CalculationState.ARCHIVED, - LocalDate.of(2025, 1, 14), - "Ldap User 2"), - new Calculation(3L, - memberPersistenceServiceIT.getAll().getLast(), - rolePersistenceServiceIT.getAll().getLast(), - CalculationState.ACTIVE, - null, - null)); + return CALCULATIONS; } @DisplayName("Should only have one active Calculation after save when member already has active Calculation for the same role.") diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificatePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificatePersistenceServiceIT.java index 9b76e0391..c1cf4795f 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificatePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificatePersistenceServiceIT.java @@ -1,11 +1,11 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.*; import static org.junit.jupiter.api.Assertions.*; import ch.puzzle.pcts.model.certificate.Certificate; import ch.puzzle.pcts.model.certificatetype.CertificateKind; import ch.puzzle.pcts.model.certificatetype.CertificateType; -import ch.puzzle.pcts.model.certificatetype.Tag; import ch.puzzle.pcts.model.member.EmploymentState; import ch.puzzle.pcts.model.member.Member; import ch.puzzle.pcts.model.organisationunit.OrganisationUnit; @@ -15,12 +15,8 @@ import ch.puzzle.pcts.repository.OrganisationUnitRepository; import jakarta.transaction.Transactional; import java.math.BigDecimal; -import java.time.Instant; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; -import java.util.Set; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -29,172 +25,33 @@ class CertificatePersistenceServiceIT extends PersistenceBaseIT { - @Autowired - CertificatePersistenceServiceIT(CertificatePersistenceService service) { - super(service); - } - @Autowired private MemberRepository memberRepository; - @Autowired private CertificateTypeRepository certificateTypeRepository; - @Autowired private OrganisationUnitRepository organisationUnitRepository; - @Override - Certificate getModel() { - return createCertificate(null, - createMember(1L, - "Member 1", - "M1", - LocalDate.of(2021, 7, 15), - LocalDate.of(1999, 8, 10), - createOrganisationUnit(1L, - "OrganisationUnit 1", - LocalDateTime - .ofInstant(Instant.EPOCH, - ZoneOffset.UTC))), - createCertificateType(1L, - "Certificate Type 1", - new BigDecimal("5.5"), - "This is Certificate 1", - Set.of(new Tag(1L, "Tag 1"))), - LocalDate.of(2021, 7, 15), - LocalDate.of(2021, 7, 15), - "Comment"); + @Autowired + CertificatePersistenceServiceIT(CertificatePersistenceService service) { + super(service); } @Override - List getAll() { - Certificate certificate1 = createCertificate(1L, - createMember(1L, - "Member 1", - "M1", - LocalDate.of(2021, 7, 15), - LocalDate.of(1999, 8, 10), - createOrganisationUnit(1L, - "OrganisationUnit 1", - LocalDateTime - .of(1970, - 1, - 1, - 0, - 0))), - createCertificateType(1L, - "Certificate Type 1", - new BigDecimal("5.5"), - "This is Certificate 1", - Set.of(new Tag(1L, "Tag 1"))), - LocalDate.of(2023, 1, 15), - LocalDate.of(2025, 1, 14), - "Completed first aid training."); - Certificate certificate2 = createCertificate(2L, - createMember(2L, - "Member 2", - "M2", - LocalDate.of(2020, 6, 1), - LocalDate.of(1998, 3, 3), - createOrganisationUnit(2L, - "OrganisationUnit 2", - null)), - createCertificateType(2L, - "Certificate Type 2", - new BigDecimal("1"), - "This is Certificate 2", - Set.of(new Tag(2L, "Longer tag name"))), - LocalDate.of(2022, 11, 1), - null, - "Completed first aid training."); - Certificate certificate3 = createCertificate(3L, - createMember(2L, - "Member 2", - "M2", - LocalDate.of(2020, 6, 1), - LocalDate.of(1998, 3, 3), - createOrganisationUnit(2L, - "OrganisationUnit 2", - null)), - createCertificateType(1L, - "Certificate Type 1", - new BigDecimal("5.5"), - "This is Certificate 1", - Set.of(new Tag(1L, "Tag 1"))), - LocalDate.of(2023, 1, 15), - LocalDate.of(2025, 1, 14), - null); - Certificate certificate4 = createCertificate(4L, - createMember(1L, - "Member 1", - "M1", - LocalDate.of(2021, 7, 15), - LocalDate.of(1999, 8, 10), - createOrganisationUnit(1L, - "OrganisationUnit 1", - LocalDateTime - .of(1970, - 1, - 1, - 0, - 0))), - createCertificateType(2L, - "Certificate Type 2", - new BigDecimal("1"), - "This is Certificate 2", - Set.of(new Tag(2L, "Longer tag name"))), - LocalDate.of(2010, 8, 12), - LocalDate.of(2023, 3, 25), - "Left organization."); - - return List.of(certificate1, certificate2, certificate3, certificate4); - } - - private Member createMember(Long id, String firstName, String abbreviation, LocalDate dateOfHire, - LocalDate birthDate, OrganisationUnit organisationUnit) { - return Member.Builder + Certificate getModel() { + return Certificate.Builder .builder() - .withId(id) - .withFirstName(firstName) - .withLastName("Test") - .withEmploymentState(EmploymentState.MEMBER) - .withAbbreviation(abbreviation) - .withDateOfHire(dateOfHire) - .withBirthDate(birthDate) - .withOrganisationUnit(organisationUnit) + .withId(null) + .withMember(MEMBER_1) + .withCompletedAt(LocalDate.of(2021, 7, 15)) + .withValidUntil(LocalDate.of(2021, 7, 15)) + .withCertificateType(CERT_TYPE_1) .build(); } - private OrganisationUnit createOrganisationUnit(Long id, String name, LocalDateTime deletedAt) { - OrganisationUnit organisationUnit = new OrganisationUnit(id, name); - organisationUnit.setDeletedAt(deletedAt); - return organisationUnit; - } - - private CertificateType createCertificateType(Long id, String name, BigDecimal points, String comment, - Set tags) { - CertificateType certificateType = new CertificateType(); - certificateType.setId(id); - certificateType.setName(name); - certificateType.setPoints(points); - certificateType.setComment(comment); - certificateType.setTags(tags); - certificateType.setCertificateKind(CertificateKind.CERTIFICATE); - certificateType.setDeletedAt(null); - return certificateType; - } - - private Certificate createCertificate(Long id, Member member, CertificateType certificateType, - LocalDate completedAt, LocalDate validUntil, String comment) { - return Certificate.Builder - .builder() - .withId(id) - .withMember(member) - .withCertificateType(certificateType) - .withCompletedAt(completedAt) - .withValidUntil(validUntil) - .withComment(comment) - .build(); + @Override + List getAll() { + return CERTIFICATES; } @Test diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificateTypePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificateTypePersistenceServiceIT.java index bd946240d..f2b714f0c 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificateTypePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/CertificateTypePersistenceServiceIT.java @@ -1,6 +1,7 @@ package ch.puzzle.pcts.service.persistence; import static ch.puzzle.pcts.Constants.CERTIFICATE_TYPE; +import static ch.puzzle.pcts.util.TestData.CERTIFICATE_TYPES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -41,49 +42,7 @@ CertificateType getModel() { @Override List getAll() { - return List - .of(new CertificateType(1L, - "Certificate Type 1", - BigDecimal.valueOf(5.5), - "This is Certificate 1", - Set.of(new Tag(1L, "Tag 1")), - CertificateKind.CERTIFICATE), - new CertificateType(2L, - "Certificate Type 2", - BigDecimal.valueOf(1), - "This is Certificate 2", - Set.of(new Tag(2L, "Longer tag name")), - CertificateKind.CERTIFICATE), - new CertificateType(3L, - "Certificate Type 3", - BigDecimal.valueOf(3), - "This is Certificate 3", - Set.of(), - CertificateKind.CERTIFICATE), - new CertificateType(4L, - "Certificate Type 4", - BigDecimal.valueOf(0.5), - "This is Certificate 4", - Set.of(), - CertificateKind.CERTIFICATE), - new CertificateType(5L, - "LeadershipExperience Type 1", - BigDecimal.valueOf(5.5), - "This is LeadershipExperience 1", - Set.of(), - CertificateKind.MILITARY_FUNCTION), - new CertificateType(6L, - "LeadershipExperience Type 2", - BigDecimal.valueOf(1), - "This is LeadershipExperience 2", - Set.of(), - CertificateKind.YOUTH_AND_SPORT), - new CertificateType(7L, - "LeadershipExperience Type 3", - BigDecimal.valueOf(3), - "This is LeadershipExperience 3", - Set.of(), - CertificateKind.LEADERSHIP_TRAINING)); + return CERTIFICATE_TYPES; } @Override diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreePersistenceServiceIT.java index eab7ad6ea..9a752166b 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreePersistenceServiceIT.java @@ -1,16 +1,10 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.*; + import ch.puzzle.pcts.model.degree.Degree; -import ch.puzzle.pcts.model.degreetype.DegreeType; -import ch.puzzle.pcts.model.member.EmploymentState; -import ch.puzzle.pcts.model.member.Member; -import ch.puzzle.pcts.model.organisationunit.OrganisationUnit; import ch.puzzle.pcts.repository.DegreeRepository; -import java.math.BigDecimal; -import java.time.Instant; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -23,35 +17,14 @@ public class DegreePersistenceServiceIT extends PersistenceBaseIT getAll() { - OrganisationUnit deletedOrganisationUnit = new OrganisationUnit(1L, "OrganisationUnit 1"); - deletedOrganisationUnit.setDeletedAt(LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)); - - OrganisationUnit organisationUnit2 = new OrganisationUnit(2L, "OrganisationUnit 2"); - - DegreeType degreeType1 = new DegreeType(1L, - "Degree type 1", - new BigDecimal("120.55"), - new BigDecimal("60"), - new BigDecimal("15.45")); - - DegreeType degreeType2 = new DegreeType(2L, - "Degree type 2", - new BigDecimal("12"), - new BigDecimal("3.961"), - new BigDecimal("3")); - - Member member1 = Member.Builder - .builder() - .withId(1L) - .withFirstName("Member 1") - .withLastName("Test") - .withEmploymentState(EmploymentState.MEMBER) - .withAbbreviation("M1") - .withDateOfHire(LocalDate.of(2021, 7, 15)) - .withBirthDate(LocalDate.of(1999, 8, 10)) - .withOrganisationUnit(deletedOrganisationUnit) - .build(); - - Member member2 = Member.Builder - .builder() - .withId(2L) - .withFirstName("Member 2") - .withLastName("Test") - .withEmploymentState(EmploymentState.MEMBER) - .withAbbreviation("M2") - .withDateOfHire(LocalDate.of(2020, 6, 1)) - .withBirthDate(LocalDate.of(1998, 3, 3)) - .withOrganisationUnit(organisationUnit2) - .build(); - - return List - .of(Degree.Builder - .builder() - .withId(1L) - .withMember(member1) - .withName("Degree 1") - .withInstitution("Institution") - .withCompleted(true) - .withDegreeType(degreeType1) - .withStartDate(LocalDate.of(2015, 9, 1)) - .withEndDate(LocalDate.of(2020, 6, 1)) - .withComment("Comment") - .build(), - - Degree.Builder - .builder() - .withId(2L) - .withMember(member2) - .withName("Degree 2") - .withInstitution("Institution") - .withCompleted(false) - .withDegreeType(degreeType2) - .withStartDate(LocalDate.of(2016, 9, 1)) - .withEndDate(LocalDate.of(2019, 6, 30)) - .withComment("Comment") - .build()); + return DEGREES; } } diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreeTypePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreeTypePersistenceServiceIT.java index ab526be8d..de6e02b63 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreeTypePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/DegreeTypePersistenceServiceIT.java @@ -1,5 +1,7 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.DEGREE_TYPES; + import ch.puzzle.pcts.model.degreetype.DegreeType; import ch.puzzle.pcts.repository.DegreeTypeRepository; import java.math.BigDecimal; @@ -26,16 +28,6 @@ DegreeType getModel() { @Override List getAll() { - return List - .of(new DegreeType(1L, - "Degree type 1", - BigDecimal.valueOf(120.55), - BigDecimal.valueOf(60), - BigDecimal.valueOf(15.45)), - new DegreeType(2L, - "Degree type 2", - BigDecimal.valueOf(12), - BigDecimal.valueOf(3.961), - BigDecimal.valueOf(3))); + return DEGREE_TYPES; } } \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperiencePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperiencePersistenceServiceIT.java index 679c5183c..4855290f2 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperiencePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperiencePersistenceServiceIT.java @@ -1,37 +1,30 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.*; + import ch.puzzle.pcts.model.experience.Experience; -import ch.puzzle.pcts.model.experiencetype.ExperienceType; -import ch.puzzle.pcts.model.member.Member; import ch.puzzle.pcts.repository.ExperienceRepository; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; public class ExperiencePersistenceServiceIT extends PersistenceBaseIT { - private final List members; - private final List experienceTypes; @Autowired - ExperiencePersistenceServiceIT(ExperiencePersistenceService service, - MemberPersistenceService memberPersistenceService, - ExperienceTypePersistenceService experienceTypePersistenceService) { + ExperiencePersistenceServiceIT(ExperiencePersistenceService service) { super(service); - this.members = new MemberPersistenceServiceIT(memberPersistenceService).getAll(); - this.experienceTypes = new ExperienceTypePersistenceServiceIT(experienceTypePersistenceService).getAll(); } @Override Experience getModel() { return new Experience.Builder() - .withMember(members.getFirst()) + .withMember(MEMBER_1) .withName("Experience 4") .withEmployer("Employer 4") .withPercent(100) - .withType(experienceTypes.getFirst()) + .withType(EXP_TYPE_1) .withComment("Comment test 4") .withStartDate(LocalDate.of(2021, 7, 15)) .withEndDate(LocalDate.of(2022, 7, 15)) @@ -39,33 +32,6 @@ Experience getModel() { } List getAll() { - List experiences = new ArrayList<>(); - experiences - .add(new Experience.Builder() - .withId(2L) - .withMember(members.getFirst()) - .withName("Experience 2") - .withEmployer("Employer 2") - .withPercent(80) - .withType(experienceTypes.get(1)) - .withComment("Comment test 2") - .withStartDate(LocalDate.of(2022, 7, 16)) - .withEndDate(LocalDate.of(2023, 7, 15)) - .build()); - - experiences - .add(new Experience.Builder() - .withId(3L) - .withMember(members.get(1)) - .withName("Experience 3") - .withEmployer("Employer 3") - .withPercent(60) - .withType(experienceTypes.getFirst()) - .withComment("Comment test 3") - .withStartDate(LocalDate.of(2023, 7, 16)) - .withEndDate(LocalDate.of(2024, 7, 15)) - .build()); - - return experiences; + return EXPERIENCES; } } \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperienceTypePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperienceTypePersistenceServiceIT.java index b79899517..2205a8747 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperienceTypePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/ExperienceTypePersistenceServiceIT.java @@ -1,5 +1,7 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.EXPERIENCE_TYPES; + import ch.puzzle.pcts.model.experiencetype.ExperienceType; import ch.puzzle.pcts.repository.ExperienceTypeRepository; import java.math.BigDecimal; @@ -26,16 +28,6 @@ ExperienceType getModel() { @Override List getAll() { - return List - .of(new ExperienceType(1L, - "ExperienceType 1", - BigDecimal.valueOf(0), - BigDecimal.valueOf(12), - BigDecimal.valueOf(4.005)), - new ExperienceType(2L, - "ExperienceType 2", - BigDecimal.valueOf(12), - BigDecimal.valueOf(10.7989), - BigDecimal.valueOf(6))); + return EXPERIENCE_TYPES; } } diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/LeadershipTypePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/LeadershipTypePersistenceServiceIT.java index 61f7dbd6a..aee91544f 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/LeadershipTypePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/LeadershipTypePersistenceServiceIT.java @@ -1,6 +1,7 @@ package ch.puzzle.pcts.service.persistence; import static ch.puzzle.pcts.Constants.LEADERSHIP_EXPERIENCE_TYPE; +import static ch.puzzle.pcts.util.TestData.LEADERSHIP_EXPERIENCE_TYPES; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.*; @@ -42,45 +43,7 @@ CertificateType getModel() { @Override List getAll() { - return List - .of(new CertificateType(1L, - "Certificate Type 1", - BigDecimal.valueOf(5.5), - "This is Certificate 1", - Set.of(new Tag(1L, "Tag 1"))), - new CertificateType(2L, - "Certificate Type 2", - BigDecimal.valueOf(1), - "This is Certificate 2", - Set.of(new Tag(2L, "Longer tag name"))), - new CertificateType(3L, - "Certificate Type 3", - BigDecimal.valueOf(3), - "This is Certificate 3", - Set.of()), - new CertificateType(4L, - "Certificate Type 4", - BigDecimal.valueOf(0.5), - "This is Certificate 4", - Set.of()), - new CertificateType(5L, - "LeadershipExperience Type 1", - BigDecimal.valueOf(5.5), - "This is LeadershipExperience 1", - Set.of(), - CertificateKind.MILITARY_FUNCTION), - new CertificateType(6L, - "LeadershipExperience Type 2", - BigDecimal.valueOf(1), - "This is LeadershipExperience 2", - Set.of(), - CertificateKind.YOUTH_AND_SPORT), - new CertificateType(7L, - "LeadershipExperience Type 3", - BigDecimal.valueOf(3), - "This is LeadershipExperience 3", - Set.of(), - CertificateKind.LEADERSHIP_TRAINING)); + return LEADERSHIP_EXPERIENCE_TYPES; } @Override diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceServiceIT.java new file mode 100644 index 000000000..4421287e6 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberOverviewPersistenceServiceIT.java @@ -0,0 +1,79 @@ +package ch.puzzle.pcts.service.persistence; + +import static ch.puzzle.pcts.util.TestData.MEMBER_1_OVERVIEWS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import ch.puzzle.pcts.dto.error.ErrorKey; +import ch.puzzle.pcts.dto.error.FieldKey; +import ch.puzzle.pcts.exception.PCTSException; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; + +class MemberOverviewPersistenceServiceIT extends PersistenceCoreIT { + + private final MemberOverviewPersistenceService service; + + @Autowired + MemberOverviewPersistenceServiceIT(MemberOverviewPersistenceService service) { + this.service = service; + } + + @DisplayName("Should get all member overview with member id 1") + @Test + void shouldGetAllMemberOverviewRowsForMember1() { + List memberOverviews = service.getById(1L); + + assertThat(memberOverviews).isNotNull(); + assertThat(memberOverviews).hasSize(4); + + assertThat(memberOverviews).allSatisfy(row -> { + assertThat(row.getMemberId()).isEqualTo(1L); + assertThat(row.getFirstName()).isEqualTo("Member 1"); + assertThat(row.getLastName()).isEqualTo("Test"); + assertThat(row.getAbbreviation()).isEqualTo("M1"); + assertThat(row.getOrganisationUnitName()).isEqualTo("OrganisationUnit 1"); + }); + + assertThat(memberOverviews) + .extracting(MemberOverview::getCertificateId) + .containsExactlyInAnyOrder(1L, 1L, 4L, 4L); + + assertThat(memberOverviews).extracting(MemberOverview::getDegreeId).containsOnly(1L); + + assertThat(memberOverviews) + .extracting(MemberOverview::getExperienceId) + .containsExactlyInAnyOrder(1L, 2L, 1L, 2L); + + assertThat(memberOverviews) + .usingRecursiveFieldByFieldElementComparator() + .containsExactlyInAnyOrderElementsOf(MEMBER_1_OVERVIEWS); + } + + @DisplayName("Should throw exception when id is not found") + @Test + void shouldThrowExceptionWhenIdIsNotFound() { + Long invalidId = -1L; + + Map expectedAttributes = Map + .of(FieldKey.FIELD, + "id", + FieldKey.IS, + String.valueOf(invalidId), + FieldKey.ENTITY, + service.entityName()); + + PCTSException exception = assertThrows(PCTSException.class, () -> service.getById(invalidId)); + + assertEquals(HttpStatus.NOT_FOUND, exception.getStatusCode()); + + assertEquals(List.of(ErrorKey.NOT_FOUND), exception.getErrorKeys()); + assertEquals(List.of(expectedAttributes), exception.getErrorAttributes()); + } +} diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberPersistenceServiceIT.java index bf6068a3f..e530e429c 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/MemberPersistenceServiceIT.java @@ -1,13 +1,12 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.MEMBERS; +import static ch.puzzle.pcts.util.TestData.ORG_UNIT_2; + import ch.puzzle.pcts.model.member.EmploymentState; import ch.puzzle.pcts.model.member.Member; -import ch.puzzle.pcts.model.organisationunit.OrganisationUnit; import ch.puzzle.pcts.repository.MemberRepository; -import java.time.Instant; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneOffset; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; @@ -29,37 +28,12 @@ Member getModel() { .withAbbreviation("M1") .withDateOfHire(LocalDate.of(2019, 8, 4)) .withBirthDate(LocalDate.of(1970, 1, 1)) - .withOrganisationUnit(new OrganisationUnit(2L, "OrganisationUnit 2")) + .withOrganisationUnit(ORG_UNIT_2) .build(); } @Override List getAll() { - OrganisationUnit deletedOrganisationUnit = new OrganisationUnit(1L, "OrganisationUnit 1"); - deletedOrganisationUnit.setDeletedAt(LocalDateTime.ofInstant(Instant.EPOCH, ZoneOffset.UTC)); - - return List - .of(Member.Builder - .builder() - .withId(1L) - .withFirstName("Member 1") - .withLastName("Test") - .withEmploymentState(EmploymentState.MEMBER) - .withAbbreviation("M1") - .withDateOfHire(LocalDate.of(2021, 7, 15)) - .withBirthDate(LocalDate.of(1999, 8, 10)) - .withOrganisationUnit(deletedOrganisationUnit) - .build(), - Member.Builder - .builder() - .withId(2L) - .withFirstName("Member 2") - .withLastName("Test") - .withEmploymentState(EmploymentState.MEMBER) - .withAbbreviation("M2") - .withDateOfHire(LocalDate.of(2020, 6, 1)) - .withBirthDate(LocalDate.of(1998, 3, 3)) - .withOrganisationUnit(new OrganisationUnit(2L, "OrganisationUnit 2")) - .build()); + return MEMBERS; } } diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/OrganisationUnitPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/OrganisationUnitPersistenceServiceIT.java index 96ee30c40..8a963b4c2 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/OrganisationUnitPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/OrganisationUnitPersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.ORGANISATION_UNITS; import static org.assertj.core.api.Assertions.assertThat; import ch.puzzle.pcts.model.organisationunit.OrganisationUnit; @@ -29,7 +30,7 @@ OrganisationUnit getModel() { @Override List getAll() { - return List.of(new OrganisationUnit(2L, "OrganisationUnit 2")); + return ORGANISATION_UNITS; } @DisplayName("Should get organisationUnit by name") diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/RolePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/RolePersistenceServiceIT.java index 95c2a2c12..86179b286 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/RolePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/RolePersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.ROLES; import static org.assertj.core.api.Assertions.assertThat; import ch.puzzle.pcts.model.role.Role; @@ -26,7 +27,7 @@ Role getModel() { } List getAll() { - return List.of(new Role(2L, "Role 2", false)); + return ROLES; } @DisplayName("Should get role by name") diff --git a/backend/src/test/java/ch/puzzle/pcts/service/persistence/TagPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/pcts/service/persistence/TagPersistenceServiceIT.java index d9b54868e..6db4a7fd9 100644 --- a/backend/src/test/java/ch/puzzle/pcts/service/persistence/TagPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/pcts/service/persistence/TagPersistenceServiceIT.java @@ -1,5 +1,6 @@ package ch.puzzle.pcts.service.persistence; +import static ch.puzzle.pcts.util.TestData.TAGS; import static org.assertj.core.api.Assertions.assertThat; import ch.puzzle.pcts.model.certificatetype.Tag; @@ -27,7 +28,7 @@ Tag getModel() { } List getAll() { - return List.of(new Tag(1L, "Tag 1"), new Tag(2L, "Longer tag name")); + return TAGS; } @DisplayName("Should find tag by name written in different cases") diff --git a/backend/src/test/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationServiceTest.java b/backend/src/test/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationServiceTest.java new file mode 100644 index 000000000..90fd619d3 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/service/validation/MemberOverviewValidationServiceTest.java @@ -0,0 +1,39 @@ +package ch.puzzle.pcts.service.validation; + +import static org.junit.jupiter.api.Assertions.*; + +import ch.puzzle.pcts.dto.error.ErrorKey; +import ch.puzzle.pcts.dto.error.FieldKey; +import ch.puzzle.pcts.exception.PCTSException; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class MemberOverviewValidationServiceTest { + + @InjectMocks + MemberOverviewValidationService validationService; + + @DisplayName("Should be successful validateOnGet() when Id is valid") + @Test + void validateOnGetByIdShouldBeSuccessfulWhenIdIsValid() { + Long id = 1L; + assertDoesNotThrow(() -> validationService.validateOnGetById(id)); + } + + @DisplayName("Should throw exception validateOnGet() when Id is null") + @Test + void validateOnGetByIdShouldThrowExceptionWhenIdIsNull() { + Long id = null; + + PCTSException exception = assertThrows(PCTSException.class, () -> validationService.validateOnGetById(id)); + + assertEquals(List.of(ErrorKey.VALIDATION), exception.getErrorKeys()); + assertEquals(List.of(Map.of(FieldKey.FIELD, "id")), exception.getErrorAttributes()); + } +} diff --git a/backend/src/test/java/ch/puzzle/pcts/util/TestData.java b/backend/src/test/java/ch/puzzle/pcts/util/TestData.java new file mode 100644 index 000000000..611b54ce0 --- /dev/null +++ b/backend/src/test/java/ch/puzzle/pcts/util/TestData.java @@ -0,0 +1,665 @@ +package ch.puzzle.pcts.util; + +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewCvDto; +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewDto; +import ch.puzzle.pcts.dto.memberoverview.MemberOverviewMemberDto; +import ch.puzzle.pcts.dto.memberoverview.certificate.MemberOverviewCertificateDto; +import ch.puzzle.pcts.dto.memberoverview.degree.MemberOverviewDegreeDto; +import ch.puzzle.pcts.dto.memberoverview.experience.MemberOverviewExperienceDto; +import ch.puzzle.pcts.model.calculation.Calculation; +import ch.puzzle.pcts.model.calculation.CalculationState; +import ch.puzzle.pcts.model.certificate.Certificate; +import ch.puzzle.pcts.model.certificatetype.CertificateKind; +import ch.puzzle.pcts.model.certificatetype.CertificateType; +import ch.puzzle.pcts.model.certificatetype.Tag; +import ch.puzzle.pcts.model.degree.Degree; +import ch.puzzle.pcts.model.degreetype.DegreeType; +import ch.puzzle.pcts.model.experience.Experience; +import ch.puzzle.pcts.model.experiencetype.ExperienceType; +import ch.puzzle.pcts.model.member.EmploymentState; +import ch.puzzle.pcts.model.member.Member; +import ch.puzzle.pcts.model.memberoverview.MemberOverview; +import ch.puzzle.pcts.model.organisationunit.OrganisationUnit; +import ch.puzzle.pcts.model.role.Role; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Set; + +public class TestData { + + private static final LocalDateTime UNIX_EPOCH = LocalDateTime.of(1970, 1, 1, 0, 0); + + private TestData() { + } + + public static final OrganisationUnit ORG_UNIT_1; + static { + ORG_UNIT_1 = new OrganisationUnit(1L, "OrganisationUnit 1"); + ORG_UNIT_1.setDeletedAt(UNIX_EPOCH); + } + + public static final OrganisationUnit ORG_UNIT_2 = new OrganisationUnit(2L, "OrganisationUnit 2"); + + public static final Role ROLE_2 = new Role(2L, "Role 2", false); + + public static final Tag TAG_1 = new Tag(1L, "Tag 1"); + public static final Tag TAG_2 = new Tag(2L, "Longer tag name"); + + public static final Member MEMBER_1 = Member.Builder + .builder() + .withId(1L) + .withFirstName("Member 1") + .withLastName("Test") + .withEmploymentState(EmploymentState.MEMBER) + .withAbbreviation("M1") + .withDateOfHire(LocalDate.of(2021, 7, 15)) + .withBirthDate(LocalDate.of(1999, 8, 10)) + .withOrganisationUnit(ORG_UNIT_1) + .build(); + + public static final Member MEMBER_2 = Member.Builder + .builder() + .withId(2L) + .withFirstName("Member 2") + .withLastName("Test") + .withEmploymentState(EmploymentState.MEMBER) + .withAbbreviation("M2") + .withDateOfHire(LocalDate.of(2020, 6, 1)) + .withBirthDate(LocalDate.of(1998, 3, 3)) + .withOrganisationUnit(ORG_UNIT_2) + .build(); + + public static final CertificateType CERT_TYPE_1 = new CertificateType(1L, + "Certificate Type 1", + BigDecimal.valueOf(5.5), + "This is Certificate 1", + Set.of(TAG_1), + CertificateKind.CERTIFICATE); + + public static final CertificateType CERT_TYPE_2 = new CertificateType(2L, + "Certificate Type 2", + BigDecimal.valueOf(1), + "This is Certificate 2", + Set.of(TAG_2), + CertificateKind.CERTIFICATE); + + public static final CertificateType CERT_TYPE_3 = new CertificateType(3L, + "Certificate Type 3", + BigDecimal.valueOf(3), + "This is Certificate 3", + Set.of(), + CertificateKind.CERTIFICATE); + + public static final CertificateType CERT_TYPE_4 = new CertificateType(4L, + "Certificate Type 4", + BigDecimal.valueOf(0.5), + "This is Certificate 4", + Set.of(), + CertificateKind.CERTIFICATE); + + public static final CertificateType LEADERSHIP_TYPE_1 = new CertificateType(5L, + "LeadershipExperience Type 1", + BigDecimal.valueOf(5.5), + "This is LeadershipExperience 1", + Set.of(), + CertificateKind.MILITARY_FUNCTION); + + public static final CertificateType LEADERSHIP_TYPE_2 = new CertificateType(6L, + "LeadershipExperience Type 2", + BigDecimal.valueOf(1), + "This is LeadershipExperience 2", + Set.of(), + CertificateKind.YOUTH_AND_SPORT); + + public static final CertificateType LEADERSHIP_TYPE_3 = new CertificateType(7L, + "LeadershipExperience Type 3", + BigDecimal.valueOf(3), + "This is LeadershipExperience 3", + Set.of(), + CertificateKind.LEADERSHIP_TRAINING); + + public static final Certificate CERTIFICATE_1 = Certificate.Builder + .builder() + .withId(1L) + .withMember(MEMBER_1) + .withCertificateType(CERT_TYPE_1) + .withCompletedAt(LocalDate.of(2023, 1, 15)) + .withValidUntil(LocalDate.of(2025, 1, 14)) + .withComment("Completed first aid training.") + .build(); + + public static final Certificate CERTIFICATE_2 = Certificate.Builder + .builder() + .withId(2L) + .withMember(MEMBER_2) + .withCertificateType(CERT_TYPE_2) + .withCompletedAt(LocalDate.of(2022, 11, 1)) + .withValidUntil(null) + .withComment("Completed first aid training.") + .build(); + + public static final Certificate CERTIFICATE_3 = Certificate.Builder + .builder() + .withId(3L) + .withMember(MEMBER_2) + .withCertificateType(CERT_TYPE_1) + .withCompletedAt(LocalDate.of(2023, 1, 15)) + .withValidUntil(LocalDate.of(2025, 1, 14)) + .withComment(null) + .build(); + + public static final Certificate CERTIFICATE_4 = Certificate.Builder + .builder() + .withId(4L) + .withMember(MEMBER_1) + .withCertificateType(CERT_TYPE_2) + .withCompletedAt(LocalDate.of(2010, 8, 12)) + .withValidUntil(LocalDate.of(2023, 3, 25)) + .withComment("Left organization.") + .build(); + + public static final DegreeType DEGREE_TYPE_1 = new DegreeType(1L, + "Degree type 1", + BigDecimal.valueOf(120.55), + BigDecimal.valueOf(60), + BigDecimal.valueOf(15.45)); + + public static final DegreeType DEGREE_TYPE_2 = new DegreeType(2L, + "Degree type 2", + BigDecimal.valueOf(12), + BigDecimal.valueOf(3.961), + BigDecimal.valueOf(3)); + + public static final Degree DEGREE_1 = Degree.Builder + .builder() + .withId(1L) + .withMember(MEMBER_1) + .withName("Degree 1") + .withInstitution("Institution") + .withCompleted(true) + .withDegreeType(DEGREE_TYPE_1) + .withStartDate(LocalDate.of(2015, 9, 1)) + .withEndDate(LocalDate.of(2020, 6, 1)) + .withComment("Comment") + .build(); + + public static final Degree DEGREE_2 = Degree.Builder + .builder() + .withId(2L) + .withMember(MEMBER_2) + .withName("Degree 2") + .withInstitution("Institution") + .withCompleted(false) + .withDegreeType(DEGREE_TYPE_2) + .withStartDate(LocalDate.of(2016, 9, 1)) + .withEndDate(LocalDate.of(2019, 6, 30)) + .withComment("Comment") + .build(); + + public static final ExperienceType EXP_TYPE_1 = new ExperienceType(1L, + "ExperienceType 1", + BigDecimal.ZERO, + BigDecimal.valueOf(12), + BigDecimal.valueOf(4.005)); + + public static final ExperienceType EXP_TYPE_2 = new ExperienceType(2L, + "ExperienceType 2", + BigDecimal.valueOf(12), + BigDecimal.valueOf(10.7989), + BigDecimal.valueOf(6)); + private static final Experience EXPERIENCE_1; + static { + EXPERIENCE_1 = new Experience.Builder() + .withId(1L) + .withMember(MEMBER_1) + .withName("Experience 1") + .withEmployer("Employer 1") + .withPercent(100) + .withType(EXP_TYPE_1) + .withComment("Comment test 1") + .withStartDate(LocalDate.of(2021, 7, 15)) + .withEndDate(LocalDate.of(2022, 7, 15)) + .build(); + EXPERIENCE_1.setDeletedAt(UNIX_EPOCH); + } + + public static final Experience EXPERIENCE_2 = new Experience.Builder() + .withId(2L) + .withMember(MEMBER_1) + .withName("Experience 2") + .withEmployer("Employer 2") + .withPercent(80) + .withType(EXP_TYPE_2) + .withComment("Comment test 2") + .withStartDate(LocalDate.of(2022, 7, 16)) + .withEndDate(LocalDate.of(2023, 7, 15)) + .build(); + + public static final Experience EXPERIENCE_3 = new Experience.Builder() + .withId(3L) + .withMember(MEMBER_2) + .withName("Experience 3") + .withEmployer("Employer 3") + .withPercent(60) + .withType(EXP_TYPE_1) + .withComment("Comment test 3") + .withStartDate(LocalDate.of(2023, 7, 16)) + .withEndDate(LocalDate.of(2024, 7, 15)) + .build(); + + public static final List MEMBER_1_OVERVIEWS = List + .of(MemberOverview.Builder + .builder() + .withMemberId(MEMBER_1.getId()) + .withFirstName(MEMBER_1.getFirstName()) + .withLastName(MEMBER_1.getLastName()) + .withAbbreviation(MEMBER_1.getAbbreviation()) + .withEmploymentState(MEMBER_1.getEmploymentState()) + .withDateOfHire(MEMBER_1.getDateOfHire()) + .withBirthDate(MEMBER_1.getBirthDate()) + .withOrganisationUnitName(MEMBER_1.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_1.getId()) + .withCertificateCompletedAt(CERTIFICATE_1.getCompletedAt()) + .withCertificateComment(CERTIFICATE_1.getComment()) + .withCertificateTypeName(CERTIFICATE_1.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_1.getId()) + .withDegreeName(DEGREE_1.getName()) + .withDegreeStartDate(DEGREE_1.getStartDate()) + .withDegreeEndDate(DEGREE_1.getEndDate()) + .withDegreeTypeName(DEGREE_1.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_1.getId()) + .withExperienceName(EXPERIENCE_1.getName()) + .withExperienceEmployer(EXPERIENCE_1.getEmployer()) + .withExperienceStartDate(EXPERIENCE_1.getStartDate()) + .withExperienceEndDate(EXPERIENCE_1.getEndDate()) + .withExperienceComment(EXPERIENCE_1.getComment()) + .withExperienceTypeName(EXPERIENCE_1.getType().getName()) + .build(), + + MemberOverview.Builder + .builder() + .withMemberId(MEMBER_1.getId()) + .withFirstName(MEMBER_1.getFirstName()) + .withLastName(MEMBER_1.getLastName()) + .withAbbreviation(MEMBER_1.getAbbreviation()) + .withEmploymentState(MEMBER_1.getEmploymentState()) + .withDateOfHire(MEMBER_1.getDateOfHire()) + .withBirthDate(MEMBER_1.getBirthDate()) + .withOrganisationUnitName(MEMBER_1.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_1.getId()) + .withCertificateCompletedAt(CERTIFICATE_1.getCompletedAt()) + .withCertificateComment(CERTIFICATE_1.getComment()) + .withCertificateTypeName(CERTIFICATE_1.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_1.getId()) + .withDegreeName(DEGREE_1.getName()) + .withDegreeStartDate(DEGREE_1.getStartDate()) + .withDegreeEndDate(DEGREE_1.getEndDate()) + .withDegreeTypeName(DEGREE_1.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_2.getId()) + .withExperienceName(EXPERIENCE_2.getName()) + .withExperienceEmployer(EXPERIENCE_2.getEmployer()) + .withExperienceStartDate(EXPERIENCE_2.getStartDate()) + .withExperienceEndDate(EXPERIENCE_2.getEndDate()) + .withExperienceComment(EXPERIENCE_2.getComment()) + .withExperienceTypeName(EXPERIENCE_2.getType().getName()) + .build(), + + MemberOverview.Builder + .builder() + .withMemberId(MEMBER_1.getId()) + .withFirstName(MEMBER_1.getFirstName()) + .withLastName(MEMBER_1.getLastName()) + .withAbbreviation(MEMBER_1.getAbbreviation()) + .withEmploymentState(MEMBER_1.getEmploymentState()) + .withDateOfHire(MEMBER_1.getDateOfHire()) + .withBirthDate(MEMBER_1.getBirthDate()) + .withOrganisationUnitName(MEMBER_1.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_4.getId()) + .withCertificateCompletedAt(CERTIFICATE_4.getCompletedAt()) + .withCertificateComment(CERTIFICATE_4.getComment()) + .withCertificateTypeName(CERTIFICATE_4.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_1.getId()) + .withDegreeName(DEGREE_1.getName()) + .withDegreeStartDate(DEGREE_1.getStartDate()) + .withDegreeEndDate(DEGREE_1.getEndDate()) + .withDegreeTypeName(DEGREE_1.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_1.getId()) + .withExperienceName(EXPERIENCE_1.getName()) + .withExperienceEmployer(EXPERIENCE_1.getEmployer()) + .withExperienceStartDate(EXPERIENCE_1.getStartDate()) + .withExperienceEndDate(EXPERIENCE_1.getEndDate()) + .withExperienceComment(EXPERIENCE_1.getComment()) + .withExperienceTypeName(EXPERIENCE_1.getType().getName()) + .build(), + + MemberOverview.Builder + .builder() + .withMemberId(MEMBER_1.getId()) + .withFirstName(MEMBER_1.getFirstName()) + .withLastName(MEMBER_1.getLastName()) + .withAbbreviation(MEMBER_1.getAbbreviation()) + .withEmploymentState(MEMBER_1.getEmploymentState()) + .withDateOfHire(MEMBER_1.getDateOfHire()) + .withBirthDate(MEMBER_1.getBirthDate()) + .withOrganisationUnitName(MEMBER_1.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_4.getId()) + .withCertificateCompletedAt(CERTIFICATE_4.getCompletedAt()) + .withCertificateComment(CERTIFICATE_4.getComment()) + .withCertificateTypeName(CERTIFICATE_4.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_1.getId()) + .withDegreeName(DEGREE_1.getName()) + .withDegreeStartDate(DEGREE_1.getStartDate()) + .withDegreeEndDate(DEGREE_1.getEndDate()) + .withDegreeTypeName(DEGREE_1.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_2.getId()) + .withExperienceName(EXPERIENCE_2.getName()) + .withExperienceEmployer(EXPERIENCE_2.getEmployer()) + .withExperienceStartDate(EXPERIENCE_2.getStartDate()) + .withExperienceEndDate(EXPERIENCE_2.getEndDate()) + .withExperienceComment(EXPERIENCE_2.getComment()) + .withExperienceTypeName(EXPERIENCE_2.getType().getName()) + .build()); + + public static final List MEMBER_2_OVERVIEWS = List + .of(MemberOverview.Builder + .builder() + .withMemberId(MEMBER_2.getId()) + .withFirstName(MEMBER_2.getFirstName()) + .withLastName(MEMBER_2.getLastName()) + .withAbbreviation(MEMBER_2.getAbbreviation()) + .withEmploymentState(MEMBER_2.getEmploymentState()) + .withDateOfHire(MEMBER_2.getDateOfHire()) + .withBirthDate(MEMBER_2.getBirthDate()) + .withOrganisationUnitName(MEMBER_2.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_2.getId()) + .withCertificateCompletedAt(CERTIFICATE_2.getCompletedAt()) + .withCertificateComment(CERTIFICATE_2.getComment()) + .withCertificateTypeName(CERTIFICATE_2.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_2.getId()) + .withDegreeName(DEGREE_2.getName()) + .withDegreeStartDate(DEGREE_2.getStartDate()) + .withDegreeEndDate(DEGREE_2.getEndDate()) + .withDegreeTypeName(DEGREE_2.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_3.getId()) + .withExperienceName(EXPERIENCE_3.getName()) + .withExperienceEmployer(EXPERIENCE_3.getEmployer()) + .withExperienceStartDate(EXPERIENCE_3.getStartDate()) + .withExperienceEndDate(EXPERIENCE_3.getEndDate()) + .withExperienceComment(EXPERIENCE_3.getComment()) + .withExperienceTypeName(EXPERIENCE_3.getType().getName()) + .build(), + + MemberOverview.Builder + .builder() + .withMemberId(MEMBER_2.getId()) + .withFirstName(MEMBER_2.getFirstName()) + .withLastName(MEMBER_2.getLastName()) + .withAbbreviation(MEMBER_2.getAbbreviation()) + .withEmploymentState(MEMBER_2.getEmploymentState()) + .withDateOfHire(MEMBER_2.getDateOfHire()) + .withBirthDate(MEMBER_2.getBirthDate()) + .withOrganisationUnitName(MEMBER_2.getOrganisationUnit().getName()) + .withCertificateId(CERTIFICATE_3.getId()) + .withCertificateCompletedAt(CERTIFICATE_3.getCompletedAt()) + .withCertificateComment(CERTIFICATE_3.getComment()) + .withCertificateTypeName(CERTIFICATE_3.getCertificateType().getName()) + .withleadershipTypeKind(CertificateKind.CERTIFICATE) + .withDegreeId(DEGREE_2.getId()) + .withDegreeName(DEGREE_2.getName()) + .withDegreeStartDate(DEGREE_2.getStartDate()) + .withDegreeEndDate(DEGREE_2.getEndDate()) + .withDegreeTypeName(DEGREE_2.getDegreeType().getName()) + .withExperienceId(EXPERIENCE_3.getId()) + .withExperienceName(EXPERIENCE_3.getName()) + .withExperienceEmployer(EXPERIENCE_3.getEmployer()) + .withExperienceStartDate(EXPERIENCE_3.getStartDate()) + .withExperienceEndDate(EXPERIENCE_3.getEndDate()) + .withExperienceComment(EXPERIENCE_3.getComment()) + .withExperienceTypeName(EXPERIENCE_3.getType().getName()) + .build()); + + public static final List MEMBER_EMPTY_CV_OVERVIEWS = List + .of(MemberOverview.Builder + .builder() + .withMemberId(MEMBER_1.getId()) + .withFirstName(MEMBER_1.getFirstName()) + .withLastName(MEMBER_1.getLastName()) + .withEmploymentState(MEMBER_1.getEmploymentState()) + .withAbbreviation(MEMBER_1.getAbbreviation()) + .withDateOfHire(MEMBER_1.getDateOfHire()) + .withBirthDate(MEMBER_1.getBirthDate()) + .withOrganisationUnitName(MEMBER_1.getOrganisationUnit().getName()) + // All CV IDs are NULL + .withCertificateId(null) + .withDegreeId(null) + .withExperienceId(null) + .build()); + + public static final MemberOverviewDto MEMBER_1_OVERVIEW_DTO = new MemberOverviewDto(new MemberOverviewMemberDto(MEMBER_1 + .getId(), + MEMBER_1 + .getFirstName(), + MEMBER_1 + .getLastName(), + MEMBER_1 + .getEmploymentState(), + MEMBER_1 + .getAbbreviation(), + MEMBER_1 + .getDateOfHire(), + MEMBER_1 + .getBirthDate(), + MEMBER_1 + .getOrganisationUnit() + .getName()), + new MemberOverviewCvDto(List + .of(new MemberOverviewDegreeDto(DEGREE_1 + .getId(), + DEGREE_1 + .getName(), + DEGREE_1 + .getDegreeType() + .getName(), + DEGREE_1 + .getStartDate(), + DEGREE_1 + .getEndDate())), + List + .of(new MemberOverviewExperienceDto(EXPERIENCE_1 + .getId(), + EXPERIENCE_1 + .getName(), + EXPERIENCE_1 + .getEmployer(), + EXPERIENCE_1 + .getType() + .getName(), + EXPERIENCE_1 + .getComment(), + EXPERIENCE_1 + .getStartDate(), + EXPERIENCE_1 + .getEndDate()), + new MemberOverviewExperienceDto(EXPERIENCE_2 + .getId(), + EXPERIENCE_2 + .getName(), + EXPERIENCE_2 + .getEmployer(), + EXPERIENCE_2 + .getType() + .getName(), + EXPERIENCE_2 + .getComment(), + EXPERIENCE_2 + .getStartDate(), + EXPERIENCE_2 + .getEndDate())), + List + .of(new MemberOverviewCertificateDto(CERTIFICATE_1 + .getId(), + CERTIFICATE_1 + .getCertificateType() + .getName(), + CERTIFICATE_1 + .getCompletedAt(), + CERTIFICATE_1 + .getComment()), + new MemberOverviewCertificateDto(CERTIFICATE_4 + .getId(), + CERTIFICATE_4 + .getCertificateType() + .getName(), + CERTIFICATE_4 + .getCompletedAt(), + CERTIFICATE_4 + .getComment())), + List + .of())); + + public static final MemberOverviewDto MEMBER_2_OVERVIEW_DTO = new MemberOverviewDto(new MemberOverviewMemberDto(MEMBER_2 + .getId(), + MEMBER_2 + .getFirstName(), + MEMBER_2 + .getLastName(), + MEMBER_2 + .getEmploymentState(), + MEMBER_2 + .getAbbreviation(), + MEMBER_2 + .getDateOfHire(), + MEMBER_2 + .getBirthDate(), + MEMBER_2 + .getOrganisationUnit() + .getName()), + new MemberOverviewCvDto(List + .of(new MemberOverviewDegreeDto(DEGREE_2 + .getId(), + DEGREE_2 + .getName(), + DEGREE_2 + .getDegreeType() + .getName(), + DEGREE_2 + .getStartDate(), + DEGREE_2 + .getEndDate())), + List + .of(new MemberOverviewExperienceDto(EXPERIENCE_3 + .getId(), + EXPERIENCE_3 + .getName(), + EXPERIENCE_3 + .getEmployer(), + EXPERIENCE_3 + .getType() + .getName(), + EXPERIENCE_3 + .getComment(), + EXPERIENCE_3 + .getStartDate(), + EXPERIENCE_3 + .getEndDate())), + List + .of(new MemberOverviewCertificateDto(CERTIFICATE_2 + .getId(), + CERTIFICATE_2 + .getCertificateType() + .getName(), + CERTIFICATE_2 + .getCompletedAt(), + CERTIFICATE_2 + .getComment()), + new MemberOverviewCertificateDto(CERTIFICATE_3 + .getId(), + CERTIFICATE_3 + .getCertificateType() + .getName(), + CERTIFICATE_3 + .getCompletedAt(), + CERTIFICATE_3 + .getComment())), + List + .of())); + + public static final MemberOverviewDto MEMBER_EMPTY_CV_DTO = new MemberOverviewDto(new MemberOverviewMemberDto(MEMBER_1 + .getId(), + MEMBER_1 + .getFirstName(), + MEMBER_1 + .getLastName(), + MEMBER_1 + .getEmploymentState(), + MEMBER_1 + .getAbbreviation(), + MEMBER_1 + .getDateOfHire(), + MEMBER_1 + .getBirthDate(), + MEMBER_1 + .getOrganisationUnit() + .getName()), + new MemberOverviewCvDto(List.of(), + List.of(), + List.of(), + List + .of())); + + public static final Calculation CALCULATION_1 = new Calculation(1L, + MEMBER_1, + ROLE_2, + CalculationState.DRAFT, + LocalDate.of(2025, 1, 14), + "Ldap User"); + + public static final Calculation CALCULATION_2 = new Calculation(2L, + MEMBER_2, + ROLE_2, + CalculationState.ARCHIVED, + LocalDate.of(2025, 1, 14), + "Ldap User 2"); + + public static final Calculation CALCULATION_3 = new Calculation(3L, + MEMBER_2, + ROLE_2, + CalculationState.ACTIVE, + null, + null); + + public static final List ORGANISATION_UNITS = List.of(ORG_UNIT_2); + + public static final List ROLES = List.of(ROLE_2); + + public static final List TAGS = List.of(TAG_1, TAG_2); + + public static final List MEMBERS = List.of(MEMBER_1, MEMBER_2); + + public static final List CERTIFICATE_TYPES = List + .of(CERT_TYPE_1, CERT_TYPE_2, CERT_TYPE_3, CERT_TYPE_4); + + public static final List LEADERSHIP_EXPERIENCE_TYPES = List + .of(LEADERSHIP_TYPE_1, LEADERSHIP_TYPE_2, LEADERSHIP_TYPE_3); + + public static final List CERTIFICATES = List + .of(CERTIFICATE_1, CERTIFICATE_2, CERTIFICATE_3, CERTIFICATE_4); + + public static final List DEGREE_TYPES = List.of(DEGREE_TYPE_1, DEGREE_TYPE_2); + + public static final List DEGREES = List.of(DEGREE_1, DEGREE_2); + + public static final List EXPERIENCE_TYPES = List.of(EXP_TYPE_1, EXP_TYPE_2); + + public static final List EXPERIENCES = List.of(EXPERIENCE_2, EXPERIENCE_3); + + public static final List CALCULATIONS = List.of(CALCULATION_1, CALCULATION_2, CALCULATION_3); + +}