diff --git a/backend/otr/src/main/java/fi/oph/otr/api/clerk/ClerkInterpreterController.java b/backend/otr/src/main/java/fi/oph/otr/api/clerk/ClerkInterpreterController.java index 37c609212..8262b6f8d 100644 --- a/backend/otr/src/main/java/fi/oph/otr/api/clerk/ClerkInterpreterController.java +++ b/backend/otr/src/main/java/fi/oph/otr/api/clerk/ClerkInterpreterController.java @@ -42,6 +42,12 @@ public List listInterpreters() { return clerkInterpreterService.list(); } + @GetMapping(path = "/missing") + @Operation(tags = TAG_INTERPRETER, summary = "List interpreters that are missing from ONR") + public List listMissingInterpreters() { + return clerkInterpreterService.listMissing(); + } + @Operation(tags = TAG_INTERPRETER, summary = "Create new interpreter") @PostMapping(consumes = APPLICATION_JSON_VALUE) @ResponseStatus(HttpStatus.CREATED) diff --git a/backend/otr/src/main/java/fi/oph/otr/onr/OnrOperationApiImpl.java b/backend/otr/src/main/java/fi/oph/otr/onr/OnrOperationApiImpl.java index 9e9187922..d3af58bd4 100644 --- a/backend/otr/src/main/java/fi/oph/otr/onr/OnrOperationApiImpl.java +++ b/backend/otr/src/main/java/fi/oph/otr/onr/OnrOperationApiImpl.java @@ -1,6 +1,7 @@ package fi.oph.otr.onr; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import fi.oph.otr.config.Constants; import fi.oph.otr.onr.dto.ContactDetailsGroupDTO; @@ -12,7 +13,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; -import lombok.RequiredArgsConstructor; import net.minidev.json.JSONArray; import org.asynchttpclient.Request; import org.asynchttpclient.RequestBuilder; @@ -21,7 +21,6 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; -@RequiredArgsConstructor public class OnrOperationApiImpl implements OnrOperationApi { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); @@ -30,6 +29,13 @@ public class OnrOperationApiImpl implements OnrOperationApi { private final String onrServiceUrl; + public OnrOperationApiImpl(final CasClient onrClient, final String onrServiceUrl) { + this.onrClient = onrClient; + this.onrServiceUrl = onrServiceUrl; + + OBJECT_MAPPER.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_AS_NULL, true); + } + @Override public Map fetchPersonalDatas(final List onrIds) throws Exception { // /henkilo/masterHenkilosByOidList might be usable as an endpoint for fetching master person data for persons diff --git a/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupSource.java b/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupSource.java index 60cf588f4..c060e2104 100644 --- a/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupSource.java +++ b/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupSource.java @@ -1,7 +1,9 @@ package fi.oph.otr.onr.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public enum ContactDetailsGroupSource { @JsonProperty("alkupera1") VTJ, diff --git a/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupType.java b/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupType.java index 6cd1314cb..0fd653488 100644 --- a/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupType.java +++ b/backend/otr/src/main/java/fi/oph/otr/onr/dto/ContactDetailsGroupType.java @@ -1,7 +1,9 @@ package fi.oph.otr.onr.dto; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +@JsonIgnoreProperties(ignoreUnknown = true) public enum ContactDetailsGroupType { @JsonProperty("yhteystietotyyppi1") KOTIOSOITE, diff --git a/backend/otr/src/main/java/fi/oph/otr/service/ClerkInterpreterService.java b/backend/otr/src/main/java/fi/oph/otr/service/ClerkInterpreterService.java index 6768d720c..4eed78aea 100644 --- a/backend/otr/src/main/java/fi/oph/otr/service/ClerkInterpreterService.java +++ b/backend/otr/src/main/java/fi/oph/otr/service/ClerkInterpreterService.java @@ -34,10 +34,13 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -69,6 +72,8 @@ public class ClerkInterpreterService { @Resource private final AuditService auditService; + private static final Logger LOG = LoggerFactory.getLogger(ClerkInterpreterService.class); + @Transactional(readOnly = true) public List list() { final List result = listWithoutAudit(); @@ -76,6 +81,15 @@ public List list() { return result; } + @Transactional(readOnly = true) + public List listMissing() { + final List interpreters = interpreterRepository.findExistingInterpreters(); + final Map personalDatas = onrService.getCachedPersonalDatas(); + auditService.logOperation(OtrOperation.LIST_INTERPRETERS); + + return interpreters.stream().map(Interpreter::getOnrId).filter(onrId -> personalDatas.get(onrId) == null).toList(); + } + private List listWithoutAudit() { final Map> interpreterRegionProjections = regionRepository .listInterpreterRegionProjections() @@ -105,16 +119,22 @@ private List listWithoutAudit() { return createClerkInterpreterDTO(interpreter, personalData, qualifications, regionProjections); }) + .filter(Optional::isPresent) + .map(Optional::get) .sorted(Comparator.comparing(ClerkInterpreterDTO::lastName).thenComparing(ClerkInterpreterDTO::nickName)) .toList(); } - private ClerkInterpreterDTO createClerkInterpreterDTO( + private Optional createClerkInterpreterDTO( final Interpreter interpreter, final PersonalData personalData, final List qualifications, final List regionProjections ) { + if (personalData == null) { + LOG.error("Personal data by onr id {} not found", interpreter.getOnrId()); + return Optional.empty(); + } final List regions = regionProjections.stream().map(InterpreterRegionProjection::code).toList(); final List qualificationDTOs = qualifications @@ -127,30 +147,32 @@ private ClerkInterpreterDTO createClerkInterpreterDTO( .toList(); final ClerkInterpreterQualificationsDTO interpreterQualificationsDTO = splitQualificationDTOs(qualificationDTOs); - return ClerkInterpreterDTO - .builder() - .id(interpreter.getId()) - .version(interpreter.getVersion()) - .isIndividualised(personalData.getIndividualised()) - .hasIndividualisedAddress(personalData.getHasIndividualisedAddress()) - .identityNumber(personalData.getIdentityNumber()) - .lastName(personalData.getLastName()) - .firstName(personalData.getFirstName()) - .nickName(personalData.getNickName()) - .email(personalData.getEmail()) - .permissionToPublishEmail(interpreter.isPermissionToPublishEmail()) - .phoneNumber(personalData.getPhoneNumber()) - .permissionToPublishPhone(interpreter.isPermissionToPublishPhone()) - .otherContactInfo(interpreter.getOtherContactInformation()) - .permissionToPublishOtherContactInfo(interpreter.isPermissionToPublishOtherContactInfo()) - .street(personalData.getStreet()) - .postalCode(personalData.getPostalCode()) - .town(personalData.getTown()) - .country(personalData.getCountry()) - .extraInformation(interpreter.getExtraInformation()) - .regions(regions) - .qualifications(interpreterQualificationsDTO) - .build(); + return Optional.of( + ClerkInterpreterDTO + .builder() + .id(interpreter.getId()) + .version(interpreter.getVersion()) + .isIndividualised(personalData.getIndividualised()) + .hasIndividualisedAddress(personalData.getHasIndividualisedAddress()) + .identityNumber(personalData.getIdentityNumber()) + .lastName(personalData.getLastName()) + .firstName(personalData.getFirstName()) + .nickName(personalData.getNickName()) + .email(personalData.getEmail()) + .permissionToPublishEmail(interpreter.isPermissionToPublishEmail()) + .phoneNumber(personalData.getPhoneNumber()) + .permissionToPublishPhone(interpreter.isPermissionToPublishPhone()) + .otherContactInfo(interpreter.getOtherContactInformation()) + .permissionToPublishOtherContactInfo(interpreter.isPermissionToPublishOtherContactInfo()) + .street(personalData.getStreet()) + .postalCode(personalData.getPostalCode()) + .town(personalData.getTown()) + .country(personalData.getCountry()) + .extraInformation(interpreter.getExtraInformation()) + .regions(regions) + .qualifications(interpreterQualificationsDTO) + .build() + ); } private ClerkQualificationDTO createQualificationDTO(final Qualification qualification) { @@ -341,7 +363,7 @@ public ClerkInterpreterDTO getInterpreter(final long interpreterId) { private ClerkInterpreterDTO getInterpreterWithoutAudit(final long interpreterId) { // This could be optimized, by fetching only one interpreter and it's data, but is it worth of the programming work? - for (ClerkInterpreterDTO i : listWithoutAudit()) { + for (final ClerkInterpreterDTO i : listWithoutAudit()) { if (i.id() == interpreterId) { return i; } diff --git a/backend/otr/src/main/java/fi/oph/otr/service/PublicInterpreterService.java b/backend/otr/src/main/java/fi/oph/otr/service/PublicInterpreterService.java index 512259251..a89ff67b1 100644 --- a/backend/otr/src/main/java/fi/oph/otr/service/PublicInterpreterService.java +++ b/backend/otr/src/main/java/fi/oph/otr/service/PublicInterpreterService.java @@ -15,8 +15,11 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -36,6 +39,8 @@ public class PublicInterpreterService { @Resource private final OnrService onrService; + private static final Logger LOG = LoggerFactory.getLogger(PublicInterpreterService.class); + @Transactional(readOnly = true) public List list() { final Map> interpreterRegionProjections = regionRepository @@ -65,18 +70,24 @@ public List list() { return toDTO(interpreter, personalData, regionProjections, qualificationProjections); }) + .filter(Optional::isPresent) + .map(Optional::get) .collect(Collectors.toCollection(ArrayList::new)); Collections.shuffle(interpreterDTOS); return interpreterDTOS; } - private InterpreterDTO toDTO( + private Optional toDTO( final Interpreter interpreter, final PersonalData personalData, final List regionProjections, final List qualificationProjections ) { + if (personalData == null) { + LOG.error("Personal data by onr id {} not found", interpreter.getOnrId()); + return Optional.empty(); + } final List regions = regionProjections.stream().map(InterpreterRegionProjection::code).toList(); final List languagePairs = qualificationProjections @@ -84,18 +95,20 @@ private InterpreterDTO toDTO( .map(qp -> LanguagePairDTO.builder().from(qp.fromLang()).to(qp.toLang()).build()) .toList(); - return InterpreterDTO - .builder() - .id(interpreter.getId()) - .firstName(personalData.getNickName()) - .lastName(personalData.getLastName()) - .email(interpreter.isPermissionToPublishEmail() ? personalData.getEmail() : null) - .phoneNumber(interpreter.isPermissionToPublishPhone() ? personalData.getPhoneNumber() : null) - .otherContactInfo( - interpreter.isPermissionToPublishOtherContactInfo() ? interpreter.getOtherContactInformation() : null - ) - .regions(regions) - .languages(languagePairs) - .build(); + return Optional.of( + InterpreterDTO + .builder() + .id(interpreter.getId()) + .firstName(personalData.getNickName()) + .lastName(personalData.getLastName()) + .email(interpreter.isPermissionToPublishEmail() ? personalData.getEmail() : null) + .phoneNumber(interpreter.isPermissionToPublishPhone() ? personalData.getPhoneNumber() : null) + .otherContactInfo( + interpreter.isPermissionToPublishOtherContactInfo() ? interpreter.getOtherContactInformation() : null + ) + .regions(regions) + .languages(languagePairs) + .build() + ); } } diff --git a/backend/otr/src/main/java/fi/oph/otr/service/email/ClerkEmailService.java b/backend/otr/src/main/java/fi/oph/otr/service/email/ClerkEmailService.java index 6b76e39d3..da8a3e99c 100644 --- a/backend/otr/src/main/java/fi/oph/otr/service/email/ClerkEmailService.java +++ b/backend/otr/src/main/java/fi/oph/otr/service/email/ClerkEmailService.java @@ -90,7 +90,7 @@ private void createQualificationExpiryData(final Qualification qualification) { createQualificationReminder(qualification, email); } else { - LOG.warn("Personal data by onr id {} not found", interpreter.getOnrId()); + LOG.error("Personal data by onr id {} not found", interpreter.getOnrId()); } } diff --git a/backend/otr/src/test/java/fi/oph/otr/onr/OnrRequestTest.java b/backend/otr/src/test/java/fi/oph/otr/onr/OnrRequestTest.java new file mode 100644 index 000000000..2e1c789c2 --- /dev/null +++ b/backend/otr/src/test/java/fi/oph/otr/onr/OnrRequestTest.java @@ -0,0 +1,43 @@ +package fi.oph.otr.onr; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import fi.oph.otr.onr.model.PersonalData; +import fi.vm.sade.javautils.nio.cas.CasClient; +import java.io.IOException; +import java.util.Optional; +import org.asynchttpclient.Response; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.security.test.context.support.WithMockUser; + +@WithMockUser +@DataJpaTest +class OnrRequestTest { + + @Value("classpath:json/henkilo-hetu-response.json") + private org.springframework.core.io.Resource hetuRequestResponse; + + @Test + void testFindPersonByIdentityNumberDeserializes() throws Exception { + final Response response = mock(Response.class); + final CasClient casClient = mock(CasClient.class); + final OnrOperationApiImpl onrOperationApi = new OnrOperationApiImpl(casClient, "http://localhost"); + + when(casClient.executeBlocking(any())).thenReturn(response); + when(response.getStatusCode()).thenReturn(200); + when(response.getResponseBody()).thenReturn(getHetuMockJsonResponse()); + + final Optional personalDataOptional = onrOperationApi.findPersonalDataByIdentityNumber("54321-54312"); + + assertTrue(personalDataOptional.isPresent()); + } + + private String getHetuMockJsonResponse() throws IOException { + return new String(hetuRequestResponse.getInputStream().readAllBytes()); + } +} diff --git a/backend/otr/src/test/resources/json/henkilo-hetu-response.json b/backend/otr/src/test/resources/json/henkilo-hetu-response.json new file mode 100644 index 000000000..6d2d5cade --- /dev/null +++ b/backend/otr/src/test/resources/json/henkilo-hetu-response.json @@ -0,0 +1,136 @@ +{ + "id": 68338658, + "etunimet": "NORDEA", + "syntymaaika": "1981-02-21", + "kuolinpaiva": null, + "hetu": "210281-9988", + "kaikkiHetut": [ + "210281-9988" + ], + "kutsumanimi": "Nordea", + "oidHenkilo": "1.2.246.562.24.73833272757", + "oppijanumero": "1.2.246.562.24.73833272757", + "sukunimi": "DEMO", + "sukupuoli": "2", + "kotikunta": null, + "turvakielto": false, + "eiSuomalaistaHetua": false, + "passivoitu": false, + "yksiloity": false, + "yksiloityVTJ": true, + "yksilointiYritetty": true, + "duplicate": false, + "created": 1545035888736, + "modified": 1686756274731, + "vtjsynced": 1611668647507, + "kasittelijaOid": "1.2.246.562.24.29628449338", + "asiointiKieli": { + "id": 1, + "kieliKoodi": "fi", + "kieliTyyppi": "suomi" + }, + "aidinkieli": { + "id": 1, + "kieliKoodi": "fi", + "kieliTyyppi": "suomi" + }, + "kansalaisuus": [ + { + "id": 1, + "kansalaisuusKoodi": "246" + } + ], + "yhteystiedotRyhma": [ + { + "id": 81325525, + "ryhmaKuvaus": "yhteystietotyyppi12", + "ryhmaAlkuperaTieto": "alkupera17", + "readOnly": false, + "yhteystieto": [ + { + "yhteystietoTyyppi": "YHTEYSTIETO_MATKAPUHELINNUMERO", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_SAHKOPOSTI", + "yhteystietoArvo": "test@test.fi" + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_KATUOSOITE", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_KUNTA", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_POSTINUMERO", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_PUHELINNUMERO", + "yhteystietoArvo": null + } + ] + }, + { + "id": 81325532, + "ryhmaKuvaus": "yhteystietotyyppi2", + "ryhmaAlkuperaTieto": "alkupera6", + "readOnly": false, + "yhteystieto": [ + { + "yhteystietoTyyppi": "YHTEYSTIETO_KUNTA", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_MATKAPUHELINNUMERO", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_POSTINUMERO", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_KATUOSOITE", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_PUHELINNUMERO", + "yhteystietoArvo": null + }, + { + "yhteystietoTyyppi": "YHTEYSTIETO_SAHKOPOSTI", + "yhteystietoArvo": "lauratestaa@testi.fi" + } + ] + }, + { + "id": 81325521, + "ryhmaKuvaus": "yhteystietotyyppi2", + "ryhmaAlkuperaTieto": "alkupera6", + "readOnly": false, + "yhteystieto": [ + { + "yhteystietoTyyppi": "YHTEYSTIETO_SAHKOPOSTI", + "yhteystietoArvo": "testi@laura.fi" + } + ] + }, + { + "id": 81325523, + "ryhmaKuvaus": "yhteystietotyyppi2", + "ryhmaAlkuperaTieto": "alkupera6", + "readOnly": false, + "yhteystieto": [ + { + "yhteystietoTyyppi": "YHTEYSTIETO_SAHKOPOSTI", + "yhteystietoArvo": "heidi.bergstrom@oph.fi" + } + ] + } + ], + "passinumerot": [], + "kielisyys": [], + "henkiloTyyppi": "OPPIJA" +} diff --git a/frontend/packages/otr/public/i18n/fi-FI/translation.json b/frontend/packages/otr/public/i18n/fi-FI/translation.json index 1bd70b266..e53273a5f 100644 --- a/frontend/packages/otr/public/i18n/fi-FI/translation.json +++ b/frontend/packages/otr/public/i18n/fi-FI/translation.json @@ -284,7 +284,8 @@ "emptySelection": "Tyhjennä valinnat" }, "register": "Rekisteri", - "title": "Oikeustulkkirekisteri" + "title": "Oikeustulkkirekisteri", + "missingInterpreters": "Tulkkien määrä ei täsmää OTR ja ONR välillä. Oppijanumerorekisteristä puuttuvat tulkit: {{interpreters}}" }, "clerkInterpreterOverviewPage": { "title": "Oikeustulkin tiedot", diff --git a/frontend/packages/otr/src/enums/api.ts b/frontend/packages/otr/src/enums/api.ts index 4c727d2c5..e5f80502c 100644 --- a/frontend/packages/otr/src/enums/api.ts +++ b/frontend/packages/otr/src/enums/api.ts @@ -1,6 +1,7 @@ export enum APIEndpoints { PublicInterpreter = '/otr/api/v1/interpreter', ClerkInterpreter = '/otr/api/v1/clerk/interpreter', + ClerkMissingInterpreters = '/otr/api/v1/clerk/interpreter/missing', ClerkPersonSearch = '/otr/api/v1/clerk/person', ClerkUser = '/otr/api/v1/clerk/user', Qualification = '/otr/api/v1/clerk/interpreter/qualification', diff --git a/frontend/packages/otr/src/pages/ClerkHomePage.tsx b/frontend/packages/otr/src/pages/ClerkHomePage.tsx index 6f8a7b80c..4e8dfac07 100644 --- a/frontend/packages/otr/src/pages/ClerkHomePage.tsx +++ b/frontend/packages/otr/src/pages/ClerkHomePage.tsx @@ -1,8 +1,8 @@ import { Add as AddIcon } from '@mui/icons-material'; -import { Divider, Grid, Paper } from '@mui/material'; -import { FC, useState } from 'react'; +import { Alert, Divider, Grid, Paper } from '@mui/material'; +import { FC, useEffect, useState } from 'react'; import { CustomButtonLink, H1, H2, Text } from 'shared/components'; -import { APIResponseStatus, Color, Variant } from 'shared/enums'; +import { APIResponseStatus, Color, Severity, Variant } from 'shared/enums'; import { ClerkHomePageControlButtons } from 'components/clerkHomePage/ClerkHomePageControlButtons'; import { ClerkInterpreterAutocompleteFilters } from 'components/clerkInterpreter/filters/ClerkInterpreterAutocompleteFilters'; @@ -10,14 +10,23 @@ import { ClerkInterpreterToggleFilters } from 'components/clerkInterpreter/filte import { ClerkInterpreterListing } from 'components/clerkInterpreter/listing/ClerkInterpreterListing'; import { ClerkHomePageSkeleton } from 'components/skeletons/ClerkHomePageSkeleton'; import { useAppTranslation } from 'configs/i18n'; -import { useAppSelector } from 'configs/redux'; +import { useAppDispatch, useAppSelector } from 'configs/redux'; import { AppRoutes } from 'enums/app'; +import { loadClerkMissingInterpreters } from 'redux/reducers/clerkInterpreter'; import { clerkInterpretersSelector } from 'redux/selectors/clerkInterpreter'; export const ClerkHomePage: FC = () => { const [page, setPage] = useState(0); - const { interpreters, status } = useAppSelector(clerkInterpretersSelector); + const { interpreters, missingInterpreters, status, missingStatus } = + useAppSelector(clerkInterpretersSelector); const isLoading = status === APIResponseStatus.InProgress; + const dispatch = useAppDispatch(); + + useEffect(() => { + if (missingStatus === APIResponseStatus.NotStarted) { + dispatch(loadClerkMissingInterpreters()); + } + }, [dispatch, missingStatus]); const { t } = useAppTranslation({ keyPrefix: 'otr.pages.clerkHomepage' }); @@ -66,6 +75,15 @@ export const ClerkHomePage: FC = () => { + {missingInterpreters?.length > 0 && ( + + + {t('missingInterpreters', { + interpreters: missingInterpreters.join(', '), + })} + + + )} diff --git a/frontend/packages/otr/src/redux/reducers/clerkInterpreter.ts b/frontend/packages/otr/src/redux/reducers/clerkInterpreter.ts index 64a655325..4a7191f8d 100644 --- a/frontend/packages/otr/src/redux/reducers/clerkInterpreter.ts +++ b/frontend/packages/otr/src/redux/reducers/clerkInterpreter.ts @@ -11,6 +11,8 @@ import { QualificationUtils } from 'utils/qualifications'; interface ClerkInterpreterState { interpreters: Array; status: APIResponseStatus; + missingStatus: APIResponseStatus; + missingInterpreters: Array; filters: ClerkInterpreterFilters; distinctToLangs: Array; } @@ -18,6 +20,8 @@ interface ClerkInterpreterState { const initialState: ClerkInterpreterState = { interpreters: [], status: APIResponseStatus.NotStarted, + missingStatus: APIResponseStatus.NotStarted, + missingInterpreters: [], filters: { qualificationStatus: QualificationStatus.Effective, fromLang: QualificationUtils.defaultFromLang, @@ -80,6 +84,16 @@ const clerkInterpreterSlice = createSlice({ updatedInterpreters.splice(spliceIndex, 1, interpreter); state.interpreters = updatedInterpreters; }, + loadClerkMissingInterpreters(state) { + state.missingStatus = APIResponseStatus.InProgress; + }, + storeClerkMissingInterpreters(state, action: PayloadAction>) { + state.missingStatus = APIResponseStatus.Success; + state.missingInterpreters = action.payload; + }, + rejectClerkMissingInterpreters(state) { + state.missingStatus = APIResponseStatus.Error; + }, }, }); @@ -88,7 +102,10 @@ export const { addClerkInterpreterFilter, loadClerkInterpreters, rejectClerkInterpreters, + rejectClerkMissingInterpreters, resetClerkInterpreterFilters, storeClerkInterpreters, upsertClerkInterpreter, + loadClerkMissingInterpreters, + storeClerkMissingInterpreters, } = clerkInterpreterSlice.actions; diff --git a/frontend/packages/otr/src/redux/sagas/clerkInterpreter.ts b/frontend/packages/otr/src/redux/sagas/clerkInterpreter.ts index 334a9978f..898c5d57f 100644 --- a/frontend/packages/otr/src/redux/sagas/clerkInterpreter.ts +++ b/frontend/packages/otr/src/redux/sagas/clerkInterpreter.ts @@ -6,11 +6,26 @@ import { APIEndpoints } from 'enums/api'; import { ClerkInterpreterResponse } from 'interfaces/clerkInterpreter'; import { loadClerkInterpreters, + loadClerkMissingInterpreters, rejectClerkInterpreters, + rejectClerkMissingInterpreters, storeClerkInterpreters, + storeClerkMissingInterpreters, } from 'redux/reducers/clerkInterpreter'; import { SerializationUtils } from 'utils/serialization'; +function* loadClerkMissingInterpretersSaga() { + try { + const response: AxiosResponse> = yield call( + axiosInstance.get, + APIEndpoints.ClerkMissingInterpreters + ); + yield put(storeClerkMissingInterpreters(response.data)); + } catch (error) { + yield put(rejectClerkMissingInterpreters()); + } +} + function* loadClerkInterpretersSaga() { try { const response: AxiosResponse> = yield call( @@ -28,4 +43,8 @@ function* loadClerkInterpretersSaga() { export function* watchClerkInterpreters() { yield takeLatest(loadClerkInterpreters.type, loadClerkInterpretersSaga); + yield takeLatest( + loadClerkMissingInterpreters.type, + loadClerkMissingInterpretersSaga + ); } diff --git a/frontend/packages/otr/src/tests/msw/handlers.ts b/frontend/packages/otr/src/tests/msw/handlers.ts index 43414aa25..271620185 100644 --- a/frontend/packages/otr/src/tests/msw/handlers.ts +++ b/frontend/packages/otr/src/tests/msw/handlers.ts @@ -18,6 +18,9 @@ export const handlers = [ rest.get(APIEndpoints.ClerkInterpreter, (req, res, ctx) => { return res(ctx.status(200), ctx.json(clerkInterpreters10)); }), + rest.get(APIEndpoints.ClerkMissingInterpreters, (req, res, ctx) => { + return res(ctx.status(200), ctx.json([])); + }), rest.put(APIEndpoints.ClerkInterpreter, async (req, res, ctx) => { const interpreterDetails = await req.json();