Skip to content

Commit

Permalink
Merge branch 'feature/OPHKIOS-77' into feature/OPHKIOS-35
Browse files Browse the repository at this point in the history
  • Loading branch information
jrkkp committed Jun 7, 2024
2 parents f86991c + 3a9abdf commit 161e39f
Show file tree
Hide file tree
Showing 18 changed files with 264 additions and 36 deletions.
10 changes: 10 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/api/PublicController.java
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public PublicEnrollmentDTO createEnrollment(
) {
final Person person = publicAuthService.getPersonFromSession(session);

// TODO this might need separate endpoint?
if (dto.isFree() && featureFlagService.isEnabled(FeatureFlag.FREE_ENROLLMENT_FOR_HIGHEST_LEVEL_ALLOWED)) {
return publicEnrollmentService.createFreeEnrollment(dto, reservationId, person);
}

return publicEnrollmentService.createEnrollment(dto, reservationId, person);
}

Expand All @@ -110,6 +115,11 @@ public PublicEnrollmentDTO updateEnrollment(
) {
final Person person = publicAuthService.getPersonFromSession(session);

// TODO this might need separate endpoint?
if (dto.isFree() && featureFlagService.isEnabled(FeatureFlag.FREE_ENROLLMENT_FOR_HIGHEST_LEVEL_ALLOWED)) {
return publicEnrollmentService.updateEnrollmentForFree(dto, examEventId, person);
}

return publicEnrollmentService.updateEnrollmentForPayment(dto, examEventId, person);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public record PublicEnrollmentCreateDTO(
@Size(max = 255) String street,
@Size(max = 255) String postalCode,
@Size(max = 255) String town,
@Size(max = 255) String country
@Size(max = 255) String country,
Boolean isFree
)
implements EnrollmentDTOCommonFields {
public PublicEnrollmentCreateDTO {
Expand Down
5 changes: 5 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/model/Enrollment.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
Expand Down Expand Up @@ -98,6 +99,10 @@ public class Enrollment extends BaseEntity {
@OneToMany(mappedBy = "enrollment")
private List<Payment> payments = new ArrayList<>();

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "free_enrollment", referencedColumnName = "free_enrollment_id")
private FreeEnrollment freeEnrollment;

public boolean isCancelled() {
return this.status == EnrollmentStatus.CANCELED || this.status == EnrollmentStatus.CANCELED_UNFINISHED_ENROLLMENT;
}
Expand Down
42 changes: 42 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/model/FreeEnrollment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package fi.oph.vkt.model;

import fi.oph.vkt.model.type.FreeEnrollmentSource;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Entity
@Table(name = "free_enrollment")
public class FreeEnrollment extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "free_enrollment_id", nullable = false)
private long id;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "person_id", referencedColumnName = "person_id", nullable = false)
private Person person;

@Column(name = "source", nullable = false)
@Enumerated(value = EnumType.STRING)
FreeEnrollmentSource source;

@Column(name = "approved", nullable = false)
boolean approved;

@Column(name = "comment")
String comment;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

// These are used in `excelEnrollmentComparator` method under `ClerkExamEventService` so their ordering matters
public enum EnrollmentStatus {
PAID,
SHIFTED_FROM_QUEUE,
COMPLETED,
AWAITING_PAYMENT,
AWAITING_APPROVAL,
QUEUED,
CANCELED,
EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package fi.oph.vkt.model.type;

public enum FreeEnrollmentSource {
KOSKI_COMPLETED_DEGREE,
KOSKI_STUDY_RIGHT,
USER_COMPLETED_DEGREE_CERTIFICATE,
USER_STUDY_RIGHT_CERTIFICATE,
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public interface ExamEventRepository extends BaseRepository<ExamEvent> {
"SELECT new fi.oph.vkt.repository.PublicExamEventProjection(e.id, e.language, e.date, e.registrationCloses," +
" COUNT(en), e.maxParticipants)" +
" FROM ExamEvent e" +
" LEFT JOIN e.enrollments en ON en.status = 'PAID' OR en.status = 'SHIFTED_FROM_QUEUE' OR en.status = 'EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT'" +
" LEFT JOIN e.enrollments en ON en.status = 'COMPLETED' OR en.status = 'AWAITING_PAYMENT' OR en.status = 'EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT'" +
" WHERE e.level = ?1" +
" AND e.registrationCloses >= CURRENT_DATE" +
" AND e.isHidden = false" +
Expand Down Expand Up @@ -46,7 +46,7 @@ public interface ExamEventRepository extends BaseRepository<ExamEvent> {
"SELECT new fi.oph.vkt.repository.ClerkExamEventProjection(e.id, e.language, e.level, e.date," +
" e.registrationCloses, COUNT(en), e.maxParticipants, e.isHidden)" +
" FROM ExamEvent e" +
" LEFT JOIN e.enrollments en ON en.status = 'PAID' OR en.status = 'SHIFTED_FROM_QUEUE' OR en.status = 'EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT'" +
" LEFT JOIN e.enrollments en ON en.status = 'COMPLETED' OR en.status = 'AWAITING_PAYMENT' OR en.status = 'EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT'" +
" GROUP BY e.id"
)
List<ClerkExamEventProjection> listClerkExamEventProjections();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package fi.oph.vkt.repository;

import fi.oph.vkt.model.FreeEnrollment;
import org.springframework.stereotype.Repository;

@Repository
public interface FreeEnrollmentRepository extends BaseRepository<FreeEnrollment> {}
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private void setEnrollmentStatus(final Enrollment enrollment, final PaymentStatu
enrollment.setStatus(EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT);
}
}
case OK -> enrollment.setStatus(EnrollmentStatus.PAID);
case OK -> enrollment.setStatus(EnrollmentStatus.COMPLETED);
case FAIL -> {
if (enrollment.getStatus() == EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT) {
enrollment.setStatus(EnrollmentStatus.CANCELED_UNFINISHED_ENROLLMENT);
Expand Down Expand Up @@ -164,7 +164,7 @@ public String createPaymentForEnrollment(final Long enrollmentId, final Person p
throw new APIException(APIExceptionType.PAYMENT_PERSON_SESSION_MISMATCH);
}

if (enrollment.getStatus() == EnrollmentStatus.PAID) {
if (enrollment.getStatus() == EnrollmentStatus.COMPLETED) {
throw new APIException(APIExceptionType.ENROLLMENT_ALREADY_PAID);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
import fi.oph.vkt.api.dto.PublicReservationDTO;
import fi.oph.vkt.model.Enrollment;
import fi.oph.vkt.model.ExamEvent;
import fi.oph.vkt.model.FreeEnrollment;
import fi.oph.vkt.model.Person;
import fi.oph.vkt.model.Reservation;
import fi.oph.vkt.model.type.EnrollmentStatus;
import fi.oph.vkt.model.type.FreeEnrollmentSource;
import fi.oph.vkt.repository.EnrollmentRepository;
import fi.oph.vkt.repository.ExamEventRepository;
import fi.oph.vkt.repository.FreeEnrollmentRepository;
import fi.oph.vkt.repository.ReservationRepository;
import fi.oph.vkt.util.ExamEventUtil;
import fi.oph.vkt.util.PersonUtil;
Expand All @@ -23,6 +26,7 @@
import java.time.LocalDateTime;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

Expand All @@ -35,6 +39,8 @@ public class PublicEnrollmentService extends AbstractEnrollmentService {
private final PublicEnrollmentEmailService publicEnrollmentEmailService;
private final PublicReservationService publicReservationService;
private final ReservationRepository reservationRepository;
private final FreeEnrollmentRepository freeEnrollmentRepository;
private final Environment environment;

@Transactional
public PublicEnrollmentInitialisationDTO initialiseEnrollment(final long examEventId, final Person person) {
Expand Down Expand Up @@ -189,23 +195,65 @@ public PublicEnrollmentDTO createEnrollment(
dto,
examEvent,
person,
EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT
EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT,
null
);
reservationRepository.deleteById(reservationId);

return createEnrollmentDTO(enrollment);
}

@Transactional
public PublicEnrollmentDTO createFreeEnrollment(
final PublicEnrollmentCreateDTO dto,
final long reservationId,
final Person person
) {
final Reservation reservation = reservationRepository.getReferenceById(reservationId);
final ExamEvent examEvent = reservation.getExamEvent();

if (person.getId() != reservation.getPerson().getId()) {
throw new APIException(APIExceptionType.RESERVATION_PERSON_SESSION_MISMATCH);
}

// TODO validate that enrollment is actually free
// Either: check Koski/user-provided certificate
// Or: seperate API-endpoint for creating FreeEnrollment entity, that is checked here

// Validate that there are unused free enrollments

FreeEnrollment freeEnrollment = new FreeEnrollment();
freeEnrollment.setApproved(false);
freeEnrollment.setPerson(person);
freeEnrollment.setSource(FreeEnrollmentSource.KOSKI_COMPLETED_DEGREE);
freeEnrollmentRepository.saveAndFlush(freeEnrollment);

final Enrollment enrollment = createOrUpdateExistingEnrollment(
dto,
examEvent,
person,
EnrollmentStatus.AWAITING_APPROVAL,
freeEnrollment
);
reservationRepository.deleteById(reservationId);

// TODO send confirmation email

return createEnrollmentDTO(enrollment);
}

private Enrollment createOrUpdateExistingEnrollment(
final PublicEnrollmentCreateDTO dto,
final ExamEvent examEvent,
final Person person,
final EnrollmentStatus enrollmentStatus
final EnrollmentStatus enrollmentStatus,
final FreeEnrollment freeEnrollment
) {
final Enrollment enrollment = findEnrollment(examEvent, person, enrollmentRepository).orElse(new Enrollment());
enrollment.setExamEvent(examEvent);
enrollment.setPerson(person);
enrollment.setStatus(enrollmentStatus);
enrollment.setFreeEnrollment(freeEnrollment != null ? freeEnrollment : enrollment.getFreeEnrollment());

copyDtoFieldsToEnrollment(enrollment, dto);
if (dto.digitalCertificateConsent()) {
Expand All @@ -229,7 +277,13 @@ public PublicEnrollmentDTO createEnrollmentToQueue(
final Person person
) {
final ExamEvent examEvent = examEventRepository.getReferenceById(examEventId);
final Enrollment enrollment = createOrUpdateExistingEnrollment(dto, examEvent, person, EnrollmentStatus.QUEUED);
final Enrollment enrollment = createOrUpdateExistingEnrollment(
dto,
examEvent,
person,
EnrollmentStatus.QUEUED,
null
);

publicEnrollmentEmailService.sendEnrollmentToQueueConfirmationEmail(enrollment, person);

Expand All @@ -246,7 +300,7 @@ public Enrollment getEnrollmentByExamEventAndPaymentLink(final long examEventId,

final LocalDateTime expiresAt = enrollment.getPaymentLinkExpiresAt();

if (enrollment.getStatus() != EnrollmentStatus.SHIFTED_FROM_QUEUE) {
if (enrollment.getStatus() != EnrollmentStatus.AWAITING_PAYMENT) {
throw new APIException(APIExceptionType.PAYMENT_LINK_INVALID_ENROLLMENT_STATUS);
}
if (expiresAt == null || expiresAt.isBefore(LocalDateTime.now())) {
Expand All @@ -267,9 +321,36 @@ public PublicEnrollmentDTO updateEnrollmentForPayment(
dto,
examEvent,
person,
EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT
EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT,
null
);

return createEnrollmentDTO(enrollment);
}

@Transactional
public PublicEnrollmentDTO updateEnrollmentForFree(
final PublicEnrollmentCreateDTO dto,
final long examEventId,
final Person person
) {
final ExamEvent examEvent = examEventRepository.getReferenceById(examEventId);

// TODO check that validations from creation are still valid?

final Enrollment enrollment = createOrUpdateExistingEnrollment(
dto,
examEvent,
person,
EnrollmentStatus.AWAITING_APPROVAL,
null
);

// TODO This needs proper handling
if (enrollment.getFreeEnrollment() == null) {
throw new APIException(APIExceptionType.PAYMENT_VALIDATION_FAIL);
}

return createEnrollmentDTO(enrollment);
}
}
4 changes: 2 additions & 2 deletions backend/vkt/src/main/java/fi/oph/vkt/util/ExamEventUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public static long getOpenings(final ExamEvent examEvent) {
final long participants = enrollments
.stream()
.filter(e ->
e.getStatus() == EnrollmentStatus.PAID ||
e.getStatus() == EnrollmentStatus.SHIFTED_FROM_QUEUE ||
e.getStatus() == EnrollmentStatus.COMPLETED ||
e.getStatus() == EnrollmentStatus.AWAITING_PAYMENT ||
e.getStatus() == EnrollmentStatus.EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT
)
.count();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@ private static ExamEventXlsxDataRow createDataRow(final Enrollment enrollment, f

private static String statusToText(final EnrollmentStatus status) {
return switch (status) {
case PAID -> "Maksettu";
case SHIFTED_FROM_QUEUE -> "Siirretty jonosta tutkintoon";
case COMPLETED -> "Maksettu";
case AWAITING_PAYMENT -> "Siirretty jonosta tutkintoon tai maksuttomuus hylättiin";
case AWAITING_APPROVAL -> "Odottaa maksuttomuuden hyväksyntää";
case QUEUED -> "Jonossa";
case CANCELED -> "Peruttu";
case EXPECTING_PAYMENT_UNFINISHED_ENROLLMENT -> "Odottaa maksua (keskeneräinen ilmoittautuminen)";
Expand Down
Loading

0 comments on commit 161e39f

Please sign in to comment.