diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/routes/PatientRoutes.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/routes/PatientRoutes.kt index 36f08ff8..8029559b 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/routes/PatientRoutes.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/routes/PatientRoutes.kt @@ -14,6 +14,7 @@ import blue.mild.covid.vaxx.dto.response.Ok import blue.mild.covid.vaxx.dto.response.PatientDtoOut import blue.mild.covid.vaxx.dto.response.PatientRegistrationResponseDtoOut import blue.mild.covid.vaxx.error.IsinValidationException +import blue.mild.covid.vaxx.error.NoPersonalAndInsuranceNumberException import blue.mild.covid.vaxx.extensions.asContextAware import blue.mild.covid.vaxx.extensions.closestDI import blue.mild.covid.vaxx.extensions.createLogger @@ -64,29 +65,34 @@ fun NormalOpenAPIRoute.patientRoutes() { captchaService.verify(recaptchaToken, request.determineRealIp()) logger.info { "Captcha token verified." } - val patientIsinId: String? = if (patientRegistration.personalNumber != null) { - logger.info { "Validating patient in the ISIN." } - // TODO maybe validate received input before actually using ISIN - val patientValidationResult = patientValidation.validatePatient( - patientRegistration.firstName, - patientRegistration.lastName, - patientRegistration.personalNumber - ) - logger.info { "Validation completed." } + logger.info { "Validating data." } + if (patientRegistration.personalNumber == null && patientRegistration.insuranceNumber == null) { + throw NoPersonalAndInsuranceNumberException() + } + + logger.info { "Validating patient in the ISIN." } + val patientValidationResult = patientValidation.validatePatient( + patientRegistration.firstName, + patientRegistration.lastName, + patientRegistration.personalNumber, + patientRegistration.insuranceNumber + ) + logger.info { "Validation completed." } - when (patientValidationResult.status) { - PatientValidationResult.PATIENT_FOUND -> - patientValidationResult.patientId - PatientValidationResult.PATIENT_NOT_FOUND -> + val patientIsinId: String? = when (patientValidationResult.status) { + PatientValidationResult.PATIENT_FOUND -> + patientValidationResult.patientId + PatientValidationResult.PATIENT_NOT_FOUND -> + if (patientRegistration.personalNumber != null) { throw IsinValidationException(patientValidationResult) - PatientValidationResult.WAS_NOT_VERIFIED -> { - logger.warn { "Patient was not validated in isin due to some problem. Skipping ISIN validation." } + } else { + logger.warn { "Patient was not found in ISIN. Not raising error because the patient was a foreigner. Skipping ISIN validation." } null } + PatientValidationResult.WAS_NOT_VERIFIED -> { + logger.warn { "Patient was not validated in isin due to some problem. Skipping ISIN validation." } + null } - } else { - logger.info { "Personal number not set. Skipping ISIN validation" } - null } logger.info { "Saving patient to the database." } diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinRetryService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinRetryService.kt index 7d1e7562..35475d3a 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinRetryService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinRetryService.kt @@ -41,12 +41,12 @@ class IsinRetryService( for (patient in patients) { logger.info("Checking ISIN id of patient ${patient.id}") - // 1. If patient has personal number but ISIN id is not set -> try ISIN validation + // 1. If patient ISIN id is not set -> try ISIN validation val updatedPatient = if (!patient.isinId.isNullOrBlank()) { logger.info("ISIN id of patient ${patient.id} is already set, skipping ISIN validation.") patient - } else if (isinJobDto.validatePatients && !patient.personalNumber.isNullOrBlank() ) { - logger.info("Patient ${patient.id} has personal number but no ISIN id. Validating in ISIN.") + } else if (isinJobDto.validatePatients) { + logger.info("Patient ${patient.id} has no ISIN id. Validating in ISIN.") val newIsinPatientId = retryPatientValidation(patient) @@ -117,7 +117,8 @@ class IsinRetryService( val patientValidationResult = patientValidation.validatePatient( firstName = patient.firstName, lastName = patient.lastName, - personalNumber = patient.personalNumber ?: throw AssertionError { "Personal number cannot be null."}, + personalNumber = patient.personalNumber, + insuranceNumber = patient.insuranceNumber ) logger.info { diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinService.kt index 813b6e40..162dae7f 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinService.kt @@ -40,6 +40,7 @@ class IsinService( private companion object : KLogging() { const val URL_GET_PATIENT_BY_PARAMETERS = "pacienti/VyhledatDleJmenoPrijmeniRc"; + const val URL_GET_FOREIGNER_BY_INSURANCE_NUMBER = "pacienti/VyhledatCizinceDleCislaPojistence"; const val URL_UPDATE_PATIENT_INFO = "pacienti/AktualizujKontaktniUdajePacienta"; const val URL_CREATE_OR_CHANGE_VACCINATION = "vakcinace/VytvorNeboZmenVakcinaci"; const val URL_CREATE_OR_CHANGE_DOSE = "vakcinace/VytvorNeboZmenDavku"; @@ -56,14 +57,39 @@ class IsinService( personalNumber.normalizePersonalNumber() )) logger.info { "Executing ISIN HTTP call ${URL_GET_PATIENT_BY_PARAMETERS}." } - val response = isinClient.get(url) - val json = response.receive() + val json = isinClient.get(url) - return IsinGetPatientByParametersResultDto( + val result = IsinGetPatientByParametersResultDto( result = json.get("vysledek").textValue(), resultMessage = json.get("vysledekZprava")?.textValue(), patientId = json.get("pacient")?.get("id")?.textValue() ) + logger.info { + "Data from ISIN for patient ${firstName} ${lastName}, personalNumber=${personalNumber}: " + + "result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}." + } + return result + } + + override suspend fun getForeignerByInsuranceNumber( + insuranceNumber: String + ): IsinGetPatientByParametersResultDto { + val url = createIsinURL(URL_GET_FOREIGNER_BY_INSURANCE_NUMBER, parameters = listOf( + insuranceNumber.trim() + )) + logger.info { "Executing ISIN HTTP call ${URL_GET_FOREIGNER_BY_INSURANCE_NUMBER}." } + val json = isinClient.get(url) + + val result = IsinGetPatientByParametersResultDto( + result = json.get("vysledek").textValue(), + resultMessage = json.get("vysledekZprava")?.textValue(), + patientId = json.get("pacient")?.get("id")?.textValue() + ) + logger.info { + "Data from ISIN for foreigner insuranceNumber=${insuranceNumber}: " + + "result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}." + } + return result } override suspend fun tryExportPatientContactInfo(patient: PatientDtoOut, notes: String?): Boolean { diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinServiceInterface.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinServiceInterface.kt index f162e2c4..c2827aca 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinServiceInterface.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinServiceInterface.kt @@ -11,6 +11,10 @@ interface IsinServiceInterface { personalNumber: String ): IsinGetPatientByParametersResultDto + suspend fun getForeignerByInsuranceNumber( + insuranceNumber: String + ): IsinGetPatientByParametersResultDto + suspend fun tryExportPatientContactInfo( patient: PatientDtoOut, notes: String? diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinValidationService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinValidationService.kt index 7175f8cf..16013644 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinValidationService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/IsinValidationService.kt @@ -1,5 +1,6 @@ package blue.mild.covid.vaxx.service +import blue.mild.covid.vaxx.dto.internal.IsinGetPatientByParametersResultDto import blue.mild.covid.vaxx.dto.internal.IsinValidationResultDto import blue.mild.covid.vaxx.dto.internal.PatientValidationResult import mu.KLogging @@ -23,14 +24,43 @@ class IsinValidationService( override suspend fun validatePatient( firstName: String, lastName: String, - personalNumber: String + personalNumber: String?, + insuranceNumber: String? ): IsinValidationResultDto { - val result = runCatching { - isinService.getPatientByParameters( - firstName = firstName, - lastName = lastName, - personalNumber = personalNumber - ) + return runCatching { + if (personalNumber != null) { + val result = isinService.getPatientByParameters( + firstName = firstName, + lastName = lastName, + personalNumber = personalNumber + ) + convertResult(result) + } else if (insuranceNumber != null){ + val byInsuranceNumberResult = isinService.getForeignerByInsuranceNumber( + insuranceNumber = insuranceNumber + ) + val validationResult = convertResult(byInsuranceNumberResult) + if (validationResult.status == PatientValidationResult.PATIENT_FOUND) { + validationResult + } else { + logger.info { + "Foreigner was not found in ISIN by insurance number ${insuranceNumber}. Trying to find the " + + "patient by first name, last name and personal number " + } + val byPersonalNumberResult = isinService.getPatientByParameters( + firstName = firstName, + lastName = lastName, + personalNumber = insuranceNumber + ) + convertResult(byPersonalNumberResult) + } + } else { + logger.error { + "Both personal and insurance numbers are not set for patient ${firstName} ${lastName}. " + + "This should not happen. Skipping ISIN validation." + } + IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED) + } }.onSuccess { logger.info { "Data retrieval from ISIN - success." } }.onFailure { @@ -39,16 +69,15 @@ class IsinValidationService( val wrappingException = Exception("An exception ${it.javaClass.canonicalName} was thrown! - ${it.message}\n${it.stackTraceToString()}") logger.error(wrappingException) { - "Getting data from ISIN server failed for patient ${firstName}/${lastName}/${personalNumber}" + "Getting data from ISIN server failed for patient ${firstName} ${lastName}, " + + "personalNumber=${personalNumber}, insuranceNumber=${insuranceNumber}" } - }.getOrNull() ?: return IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED) + }.getOrNull() ?: IsinValidationResultDto(status = PatientValidationResult.WAS_NOT_VERIFIED) - logger.info { - "Data from ISIN for patient ${firstName}/${lastName}/${personalNumber}: " + - "result=${result.result}, resultMessage=${result.resultMessage}, patientId=${result.patientId}." - } + } - return when (result.result) { + private fun convertResult(result: IsinGetPatientByParametersResultDto): IsinValidationResultDto = + when (result.result) { VyhledaniPacientaResult.PacientNalezen.name, VyhledaniPacientaResult.NalezenoVicePacientu.name -> IsinValidationResultDto( @@ -65,5 +94,4 @@ class IsinValidationService( status = PatientValidationResult.WAS_NOT_VERIFIED ) } - } } diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/PatientValidationService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/PatientValidationService.kt index aaca1585..e7842918 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/PatientValidationService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/PatientValidationService.kt @@ -6,5 +6,10 @@ fun interface PatientValidationService { /** * Validates patient against medical system service. */ - suspend fun validatePatient(firstName: String, lastName: String, personalNumber: String): PatientValidationResultDto + suspend fun validatePatient( + firstName: String, + lastName: String, + personalNumber: String?, + insuranceNumber: String? + ): PatientValidationResultDto } diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/ValidationService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/ValidationService.kt index 9682757b..160562d7 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/ValidationService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/ValidationService.kt @@ -131,7 +131,7 @@ class ValidationService(private val questionService: QuestionService) { } } - fun requireValidPersonalOrInsuranceNumber(personalNumber: String?, insuranceNumber: String?) { + private fun requireValidPersonalOrInsuranceNumber(personalNumber: String?, insuranceNumber: String?) { if (personalNumber != null && personalNumber.isNotBlank()) requireValidPersonalNumber(personalNumber) else if (insuranceNumber != null && insuranceNumber.isNotBlank()){ diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyIsinService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyIsinService.kt index 9b945799..e580cdb5 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyIsinService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyIsinService.kt @@ -16,7 +16,14 @@ class DummyIsinService : IsinServiceInterface { personalNumber: String ): IsinGetPatientByParametersResultDto { logger.warn { "NOT GETTING patient ${firstName}/${lastName}/${personalNumber} from ISIN. This should not be in the production." } - return IsinGetPatientByParametersResultDto("PouzitiDummyISIN", null, null) + return IsinGetPatientByParametersResultDto("UsingDummyISIN", null, null) + } + + override suspend fun getForeignerByInsuranceNumber( + insuranceNumber: String + ): IsinGetPatientByParametersResultDto { + logger.warn { "NOT GETTING foreigner ${insuranceNumber} from ISIN. This should not be in the production." } + return IsinGetPatientByParametersResultDto("UsingDummyISIN", null, null) } override suspend fun tryExportPatientContactInfo( diff --git a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyPatientValidationService.kt b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyPatientValidationService.kt index 4219638e..366023bb 100644 --- a/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyPatientValidationService.kt +++ b/backend/src/main/kotlin/blue/mild/covid/vaxx/service/dummy/DummyPatientValidationService.kt @@ -12,7 +12,8 @@ class DummyPatientValidationService : PatientValidationService { override suspend fun validatePatient( firstName: String, lastName: String, - personalNumber: String + personalNumber: String?, + insuranceNumber: String? ): PatientValidationResultDto { logger.warn { "NOT VERIFYING patient ${personalNumber}. This should not be in the production." } return IsinValidationResultDto(PatientValidationResult.WAS_NOT_VERIFIED) diff --git a/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/http-client.env.json b/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/http-client.env.json index 7a999240..f6682b1d 100644 --- a/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/http-client.env.json +++ b/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/http-client.env.json @@ -10,9 +10,13 @@ "password": "bluemild" }, "test": { - "url": "https://covid-vaxx.test.mild.blue/api" + "url": "https://covid-vaxx.test.mild.blue/api", + "email": "mail", + "password": "pass" }, "production": { - "url": "https://ockovani.praha7.cz/api" + "url": "https://ockovani.praha7.cz/api", + "email": "mail", + "password": "pass" } } diff --git a/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/test-staging.http b/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/test-staging.http index 81dbd4df..b17d5b34 100644 --- a/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/test-staging.http +++ b/backend/src/test/kotlin/blue/mild/covid/vaxx/requests/test-staging.http @@ -57,9 +57,9 @@ POST {{url}}/patient?captcha=1234 Content-Type: application/json { - "firstName": "Tomáš", - "lastName": "Kos", - "insuranceNumber": "1234567890", + "firstName": "Adéla", + "lastName": "Kostičková", + "personalNumber": "9151010385", "email": "kubant.jan@example.com", "phoneNumber": { "countryCode": "CZ", @@ -129,7 +129,6 @@ Authorization: Bearer {{auth_token}} "patientId":"{{patient_id}}" } - ### // register user diff --git a/backend/src/test/kotlin/blue/mild/covid/vaxx/routes/PatientRoutesTest.kt b/backend/src/test/kotlin/blue/mild/covid/vaxx/routes/PatientRoutesTest.kt index 5fafea06..f6df3757 100644 --- a/backend/src/test/kotlin/blue/mild/covid/vaxx/routes/PatientRoutesTest.kt +++ b/backend/src/test/kotlin/blue/mild/covid/vaxx/routes/PatientRoutesTest.kt @@ -64,7 +64,7 @@ class PatientRoutesTest : ServerTestBase() { bind() with singleton { val service = mockk() coEvery { - service.validatePatient(any(), any(), any()) + service.validatePatient(any(), any(), any(), any()) } returns IsinValidationResultDto(PatientValidationResult.PATIENT_FOUND, "10") service @@ -315,7 +315,8 @@ class PatientRoutesTest : ServerTestBase() { validationService.validatePatient( validRegistration.firstName, validRegistration.lastName, - validRegistration.personalNumber ?: "" + validRegistration.personalNumber, + validRegistration.insuranceNumber ) } returns IsinValidationResultDto(PatientValidationResult.PATIENT_NOT_FOUND)