From b3327b459f960156053635cf48c6e93f7b227bbd Mon Sep 17 00:00:00 2001 From: Jarkko Pesonen <435495+jrkkp@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:52:20 +0200 Subject: [PATCH] VKT(Frontend & Backend): Enrollment appointment clerk interface --- .../clerk/ClerkEnrollmentAppointmentDTO.java | 2 +- .../ClerkEnrollmentAppointmentUpdateDTO.java | 1 - .../service/AbstractEnrollmentService.java | 7 ++++ .../vkt/service/ClerkEnrollmentService.java | 14 +++++-- .../vkt/service/PublicEnrollmentService.java | 3 -- .../fi/oph/vkt/util/ClerkEnrollmentUtil.java | 16 +++++++- .../ClerkEnrollmentAppointmentDetails.tsx | 8 +++- ...lerkEnrollmentAppointmentDetailsFields.tsx | 3 ++ .../overview/ClerkEnrollmentDetails.tsx | 2 + .../overview/ControlButtons.tsx | 10 ++--- .../vkt/src/interfaces/clerkEnrollment.ts | 1 + ...ClerkEnrollmentAppointmentOverviewPage.tsx | 5 ++- .../ClerkEnrollmentContactRequestPage.tsx | 41 +++++++++++++++++-- .../redux/sagas/clerkEnrollmentAppointment.ts | 2 +- frontend/packages/vkt/src/utils/enrollment.ts | 9 ++++ 15 files changed, 102 insertions(+), 22 deletions(-) diff --git a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentDTO.java b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentDTO.java index 0c0e798dd..5c4272b4d 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentDTO.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentDTO.java @@ -2,7 +2,6 @@ import fi.oph.vkt.api.dto.EnrollmentDTOSkillFields; import fi.oph.vkt.model.type.EnrollmentAppointmentStatus; -import fi.oph.vkt.model.type.EnrollmentStatus; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; @@ -32,6 +31,7 @@ public record ClerkEnrollmentAppointmentDTO( String country, @NonNull @NotBlank String firstName, @NonNull @NotBlank String lastName, + @NonNull @NotBlank String authLink, @NonNull @NotNull List payments ) implements EnrollmentDTOSkillFields {} diff --git a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentUpdateDTO.java b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentUpdateDTO.java index 77dc25614..9acdddbb9 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentUpdateDTO.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/api/dto/clerk/ClerkEnrollmentAppointmentUpdateDTO.java @@ -15,7 +15,6 @@ public record ClerkEnrollmentAppointmentUpdateDTO( @NonNull @NotNull Long id, @NonNull @NotNull Integer version, - @NonNull @NotNull LocalDateTime enrollmentTime, @NonNull @NotNull Boolean oralSkill, @NonNull @NotNull Boolean textualSkill, @NonNull @NotNull Boolean understandingSkill, diff --git a/backend/vkt/src/main/java/fi/oph/vkt/service/AbstractEnrollmentService.java b/backend/vkt/src/main/java/fi/oph/vkt/service/AbstractEnrollmentService.java index d39053772..6d22efd49 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/service/AbstractEnrollmentService.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/service/AbstractEnrollmentService.java @@ -30,6 +30,11 @@ protected void copyDtoFieldsToEnrollment( ) { copyDtoSkillFieldsToEnrollment(enrollment, dto); enrollment.setEmail(dto.email()); + enrollment.setPhoneNumber(dto.phoneNumber()); + enrollment.setStreet(dto.street()); + enrollment.setPostalCode(dto.postalCode()); + enrollment.setTown(dto.town()); + enrollment.setCountry(dto.country()); } protected void copyDtoFieldsToEnrollment( @@ -38,6 +43,8 @@ protected void copyDtoFieldsToEnrollment( ) { copyDtoSkillFieldsToEnrollment(enrollment, dto); enrollment.setEmail(dto.email()); + enrollment.setFirstName(dto.firstName()); + enrollment.setLastName(dto.lastName()); } protected void copyDtoFieldsToEnrollment(final Enrollment enrollment, final EnrollmentDTOCommonFields dto) { diff --git a/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkEnrollmentService.java b/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkEnrollmentService.java index e468df2f1..3f7bc231a 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkEnrollmentService.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/service/ClerkEnrollmentService.java @@ -241,11 +241,17 @@ public ClerkEnrollmentAppointmentDTO convertToAppointment(final long enrollmentC final EnrollmentAppointment enrollmentAppointment = enrollmentAppointmentRepository.getReferenceById( enrollmentContactId ); + final String baseUrlAPI = environment.getRequiredProperty("app.base-url.api"); enrollmentAppointment.setStatus(EnrollmentAppointmentStatus.WAITING_AUTHENTICATION); + + if (enrollmentAppointment.getAuthHash() == null) { + enrollmentAppointment.setAuthHash(uuidSource.getRandomNonce()); + } + enrollmentAppointmentRepository.flush(); - return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment); + return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment, baseUrlAPI); } @Transactional(readOnly = true) @@ -253,19 +259,21 @@ public ClerkEnrollmentAppointmentDTO getEnrollmentAppointment(final long enrollm final EnrollmentAppointment enrollmentAppointment = enrollmentAppointmentRepository.getReferenceById( enrollmentAppointmentId ); + final String baseUrlAPI = environment.getRequiredProperty("app.base-url.api"); - return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment); + return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment, baseUrlAPI); } @Transactional public ClerkEnrollmentAppointmentDTO updateAppointment(final ClerkEnrollmentAppointmentUpdateDTO dto) { final EnrollmentAppointment enrollmentAppointment = enrollmentAppointmentRepository.getReferenceById(dto.id()); + final String baseUrlAPI = environment.getRequiredProperty("app.base-url.api"); enrollmentAppointment.assertVersion(dto.version()); copyDtoFieldsToEnrollment(enrollmentAppointment, dto); enrollmentAppointmentRepository.flush(); - return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment); + return ClerkEnrollmentUtil.createClerkEnrollmentAppointmentDTO(enrollmentAppointment, baseUrlAPI); } } diff --git a/backend/vkt/src/main/java/fi/oph/vkt/service/PublicEnrollmentService.java b/backend/vkt/src/main/java/fi/oph/vkt/service/PublicEnrollmentService.java index a13176c51..75511fe2b 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/service/PublicEnrollmentService.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/service/PublicEnrollmentService.java @@ -683,9 +683,6 @@ public void createEnrollmentContact(final PublicEnrollmentContactCreateDTO dto) enrollmentAppointment.setStatus(EnrollmentAppointmentStatus.CONTACT_CREATED); copyDtoFieldsToEnrollment(enrollmentAppointment, dto); - // TODO: remove - enrollmentAppointment.setAuthHash("asd"); - enrollmentAppointmentRepository.saveAndFlush(enrollmentAppointment); } diff --git a/backend/vkt/src/main/java/fi/oph/vkt/util/ClerkEnrollmentUtil.java b/backend/vkt/src/main/java/fi/oph/vkt/util/ClerkEnrollmentUtil.java index 550d493c8..b7428e0d0 100644 --- a/backend/vkt/src/main/java/fi/oph/vkt/util/ClerkEnrollmentUtil.java +++ b/backend/vkt/src/main/java/fi/oph/vkt/util/ClerkEnrollmentUtil.java @@ -149,7 +149,8 @@ public static KoskiEducationsDTO createKoskiEducationsDTO(final KoskiEducations } public static ClerkEnrollmentAppointmentDTO createClerkEnrollmentAppointmentDTO( - final EnrollmentAppointment enrollmentAppointment + final EnrollmentAppointment enrollmentAppointment, + final String baseUrlAPI ) { final List paymentDTOs = enrollmentAppointment .getPayments() @@ -170,10 +171,23 @@ public static ClerkEnrollmentAppointmentDTO createClerkEnrollmentAppointmentDTO( .speechComprehensionPartialExam(enrollmentAppointment.isSpeechComprehensionPartialExam()) .writingPartialExam(enrollmentAppointment.isWritingPartialExam()) .readingComprehensionPartialExam(enrollmentAppointment.isReadingComprehensionPartialExam()) + .street(enrollmentAppointment.getStreet()) + .postalCode(enrollmentAppointment.getPostalCode()) + .town(enrollmentAppointment.getTown()) + .country(enrollmentAppointment.getCountry()) .status(enrollmentAppointment.getStatus()) .email(enrollmentAppointment.getEmail()) + .phoneNumber(enrollmentAppointment.getPhoneNumber()) .firstName(enrollmentAppointment.getFirstName()) .lastName(enrollmentAppointment.getLastName()) + .authLink( + String.format( + "%s/enrollment/appointment/%d/redirect/%s", + baseUrlAPI, + enrollmentAppointment.getId(), + enrollmentAppointment.getAuthHash() + ) + ) .payments(paymentDTOs) .build(); } diff --git a/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetails.tsx b/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetails.tsx index 1437f2a1b..3b89e269f 100644 --- a/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetails.tsx +++ b/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetails.tsx @@ -27,8 +27,10 @@ import { EnrollmentUtils } from 'utils/enrollment'; export const ClerkEnrollmentAppointmentDetails = ({ enrollment, + editMode, }: { enrollment: ClerkEnrollmentAppointment; + editMode: boolean; }) => { // Redux const dispatch = useAppDispatch(); @@ -47,7 +49,9 @@ export const ClerkEnrollmentAppointmentDetails = ({ ClerkEnrollmentAppointment | undefined >(enrollment); const [hasLocalChanges, setHasLocalChanges] = useState(false); - const [currentUIMode, setCurrentUIMode] = useState(UIMode.View); + const [currentUIMode, setCurrentUIMode] = useState( + editMode ? UIMode.Edit : UIMode.View, + ); const isViewMode = currentUIMode === UIMode.View; const resetLocalEnrollmentDetails = useCallback(() => { @@ -59,6 +63,7 @@ export const ClerkEnrollmentAppointmentDetails = ({ keyPrefix: 'vkt.component.clerkEnrollmentDetails', }); const translateCommon = useCommonTranslation(); + const isLoading = status === APIResponseStatus.InProgress; const resetToInitialState = useCallback(() => { dispatch(resetClerkEnrollmentDetailsUpdate()); @@ -233,6 +238,7 @@ export const ClerkEnrollmentAppointmentDetails = ({ onMove={handleMoveButtonClick} isViewMode={isViewMode} hasRequiredDetails={hasRequiredDetails} + isLoading={isLoading} /> } /> diff --git a/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetailsFields.tsx b/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetailsFields.tsx index c5dcd7483..a8cdca11f 100644 --- a/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetailsFields.tsx +++ b/frontend/packages/vkt/src/components/clerkEnrollment/appointment/ClerkEnrollmentAppointmentDetailsFields.tsx @@ -530,6 +530,9 @@ export const ClerkEnrollmentAppointmentDetailsFields = ({ )} )} +
+ Ilmoittautumislinkki: {enrollment.authLink} +
{ const [hasLocalChanges, setHasLocalChanges] = useState(false); const [currentUIMode, setCurrentUIMode] = useState(UIMode.View); const isViewMode = currentUIMode === UIMode.View; + const isLoading = status === APIResponseStatus.InProgress; const handleMoveButtonCLick = () => setIsOpenModalOpen(true); const closeMoveModal = () => setIsOpenModalOpen(false); @@ -281,6 +282,7 @@ export const ClerkEnrollmentDetails = () => { onMove={handleMoveButtonCLick} isViewMode={isViewMode} hasRequiredDetails={hasRequiredDetails} + isLoading={isLoading} /> } /> diff --git a/frontend/packages/vkt/src/components/clerkEnrollment/overview/ControlButtons.tsx b/frontend/packages/vkt/src/components/clerkEnrollment/overview/ControlButtons.tsx index 2d700330c..e76fe8bc9 100644 --- a/frontend/packages/vkt/src/components/clerkEnrollment/overview/ControlButtons.tsx +++ b/frontend/packages/vkt/src/components/clerkEnrollment/overview/ControlButtons.tsx @@ -1,11 +1,9 @@ import EditIcon from '@mui/icons-material/Edit'; import { FC } from 'react'; import { CustomButton, LoadingProgressIndicator } from 'shared/components'; -import { APIResponseStatus, Color, Variant } from 'shared/enums'; +import { Color, Variant } from 'shared/enums'; import { useClerkTranslation, useCommonTranslation } from 'configs/i18n'; -import { useAppSelector } from 'configs/redux'; -import { clerkEnrollmentDetailsSelector } from 'redux/selectors/clerkEnrollmentDetails'; interface ControlButtonsProps { onCancel: () => void; @@ -14,6 +12,7 @@ interface ControlButtonsProps { onMove: () => void; isViewMode: boolean; hasRequiredDetails: boolean; + isLoading: boolean; } export const ControlButtons: FC = ({ @@ -23,16 +22,13 @@ export const ControlButtons: FC = ({ onMove, isViewMode, hasRequiredDetails, + isLoading, }) => { const { t } = useClerkTranslation({ keyPrefix: 'vkt.component.clerkEnrollmentDetails.controlButtons', }); const translateCommon = useCommonTranslation(); - const { status } = useAppSelector(clerkEnrollmentDetailsSelector); - - const isLoading = status === APIResponseStatus.InProgress; - if (isViewMode) { return (
diff --git a/frontend/packages/vkt/src/interfaces/clerkEnrollment.ts b/frontend/packages/vkt/src/interfaces/clerkEnrollment.ts index 8ced11804..0c6dd095f 100644 --- a/frontend/packages/vkt/src/interfaces/clerkEnrollment.ts +++ b/frontend/packages/vkt/src/interfaces/clerkEnrollment.ts @@ -86,6 +86,7 @@ export interface ClerkEnrollmentContactResponse export interface ClerkEnrollmentAppointment extends ClerkEnrollmentContact { payments: Array; person?: ClerkPerson; + authLink: string; } export interface ClerkEnrollmentAppointmentResponse diff --git a/frontend/packages/vkt/src/pages/ClerkEnrollmentAppointmentOverviewPage.tsx b/frontend/packages/vkt/src/pages/ClerkEnrollmentAppointmentOverviewPage.tsx index afcfdf762..e81ff38f1 100644 --- a/frontend/packages/vkt/src/pages/ClerkEnrollmentAppointmentOverviewPage.tsx +++ b/frontend/packages/vkt/src/pages/ClerkEnrollmentAppointmentOverviewPage.tsx @@ -35,7 +35,10 @@ export const ClerkEnrollmentAppointmentOverviewPage: FC = () => { className="clerk-enrollment-overview-page__content-container rows" > {enrollment && ( - + )} diff --git a/frontend/packages/vkt/src/pages/ClerkEnrollmentContactRequestPage.tsx b/frontend/packages/vkt/src/pages/ClerkEnrollmentContactRequestPage.tsx index d7cfd449e..8f3b67bf4 100644 --- a/frontend/packages/vkt/src/pages/ClerkEnrollmentContactRequestPage.tsx +++ b/frontend/packages/vkt/src/pages/ClerkEnrollmentContactRequestPage.tsx @@ -16,11 +16,13 @@ import { APIResponseStatus, Color, Variant } from 'shared/enums'; import { useCommonTranslation } from 'configs/i18n'; import { useAppDispatch, useAppSelector } from 'configs/redux'; import { AppRoutes } from 'enums/app'; +import { PartialExamsAndSkills } from 'interfaces/common/enrollment'; import { createClerkEnrollmentAppointment, loadClerkEnrollmentContactRequest, } from 'redux/reducers/clerkEnrollmentContactRequest'; import { clerkEnrollmentContactRequestSelector } from 'redux/selectors/clerkEnrollmentContactRequest'; +import { EnrollmentUtils } from 'utils/enrollment'; const BackButton = () => { const translateCommon = useCommonTranslation(); @@ -80,6 +82,31 @@ export const ClerkEnrollmentContactRequestPage: FC = () => { return <>; } + const partialExamsToText = (skills: PartialExamsAndSkills) => { + return [ + skills.writingPartialExam + ? translateCommon('enrollment.partialExamsAndSkills.writingPartialExam') + : false, + skills.readingComprehensionPartialExam + ? translateCommon( + 'enrollment.partialExamsAndSkills.readingComprehensionPartialExam', + ) + : false, + skills.speakingPartialExam + ? translateCommon( + 'enrollment.partialExamsAndSkills.speakingPartialExam', + ) + : false, + skills.speechComprehensionPartialExam + ? translateCommon( + 'enrollment.partialExamsAndSkills.speechComprehensionPartialExam', + ) + : false, + ] + .filter((skill) => skill) + .join(', '); + }; + const onSubmit = () => { dispatch(createClerkEnrollmentAppointment(enrollment.id)); }; @@ -122,15 +149,23 @@ export const ClerkEnrollmentContactRequestPage: FC = () => {

Yhteydenoton tiedot

Haluan suorittaa koko tutkinnon?

- Ei + + {EnrollmentUtils.isFullExam(enrollment) + ? translateCommon('yes') + : translateCommon('no')} +

Osakokeet, jotka haluan suorittaa

- Ei + {partialExamsToText(enrollment)}

Osallistunut aiempiin tutkintoihin?

- Ei + + {enrollment.previousEnrollment + ? translateCommon('yes') + : translateCommon('no')} +

Viesti

diff --git a/frontend/packages/vkt/src/redux/sagas/clerkEnrollmentAppointment.ts b/frontend/packages/vkt/src/redux/sagas/clerkEnrollmentAppointment.ts index 9a9243e0f..b03d94757 100644 --- a/frontend/packages/vkt/src/redux/sagas/clerkEnrollmentAppointment.ts +++ b/frontend/packages/vkt/src/redux/sagas/clerkEnrollmentAppointment.ts @@ -30,7 +30,7 @@ function* updateClerkEnrollmentAppointmentSaga( const apiResponse: AxiosResponse = yield call( axiosInstance.put, - APIEndpoints.ClerkEnrollment, + `${APIEndpoints.ClerkEnrollmentAppointment}/${enrollment.id}`, SerializationUtils.serializeClerkEnrollmentAppointment(enrollment), ); const updatedEnrollment = diff --git a/frontend/packages/vkt/src/utils/enrollment.ts b/frontend/packages/vkt/src/utils/enrollment.ts index a1bc5fe5f..180019fa8 100644 --- a/frontend/packages/vkt/src/utils/enrollment.ts +++ b/frontend/packages/vkt/src/utils/enrollment.ts @@ -14,6 +14,15 @@ import { import { PublicEnrollment } from 'interfaces/publicEnrollment'; export class EnrollmentUtils { + static isFullExam(skills: PartialExamsAndSkills) { + return ( + skills.writingPartialExam && + skills.readingComprehensionPartialExam && + skills.speakingPartialExam && + skills.speechComprehensionPartialExam + ); + } + static isValidTextualSkillAndPartialExams(skills: PartialExamsAndSkills) { return skills.textualSkill ? skills.writingPartialExam || skills.readingComprehensionPartialExam