Skip to content

Commit

Permalink
IS-2871: Add possibility to search on name
Browse files Browse the repository at this point in the history
  • Loading branch information
vetlesolgaard committed Dec 20, 2024
1 parent c90aa53 commit c709042
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ fun Route.registerPersonoversiktApiV2(
?: throw java.lang.IllegalArgumentException("No Authorization header supplied")
val searchQuery = call.receive<SearchQueryDTO>().toSearchQuery()

val searchResult = personoversiktSearchService.searchSykmeldt(searchQuery = searchQuery)
val searchResult = personoversiktSearchService.searchSykmeldt(search = searchQuery)
val fnrWithVeilederAccess = veilederTilgangskontrollClient.veilederPersonAccessListMedOBO(
personIdentNumberList = searchResult.map { it.fnr },
personidenter = searchResult.map { it.fnr },
token = token,
callId = callId,
) ?: emptyList()
Expand Down Expand Up @@ -94,7 +94,7 @@ fun Route.registerPersonoversiktApiV2(

val personFnrListWithVeilederAccess: List<String> =
veilederTilgangskontrollClient.veilederPersonAccessListMedOBO(
personIdentNumberList = personOversiktStatusList.map { it.fnr },
personidenter = personOversiktStatusList.map { it.fnr },
token = token,
callId = callId,
) ?: emptyList()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
package no.nav.syfo.personstatus.api.v2.model

import no.nav.syfo.personstatus.domain.Initials
import no.nav.syfo.personstatus.domain.SearchQuery
import no.nav.syfo.personstatus.domain.Name
import no.nav.syfo.personstatus.domain.Search
import java.time.LocalDate

data class SearchQueryDTO(
val initials: String?,
val birthdate: LocalDate,
val initials: String? = null,
val name: String? = null,
val birthdate: LocalDate? = null,
) {
fun toSearchQuery(): SearchQuery = SearchQuery(
birthdate = birthdate,
initials = Initials(
initials
)
)
fun toSearchQuery(): Search =
if (name != null && birthdate != null) {
Search.ByNameAndDate(
name = Name(name),
birthdate = birthdate
)
} else if (initials != null && initials.isNotEmpty() && birthdate != null) {
Search.ByInitialsAndDate(initials = Initials(initials), birthdate = birthdate)
} else if (name != null) {
Search.ByName(name = Name(name))
} else if (birthdate != null) {
Search.ByDate(birthdate = birthdate)
} else {
throw IllegalArgumentException("SearchQueryDTO values did not conform to allowed search rules")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import no.nav.syfo.oppfolgingstilfelle.domain.Oppfolgingstilfelle
import no.nav.syfo.personstatus.api.v2.model.VeilederTildelingHistorikkDTO
import no.nav.syfo.personstatus.domain.PersonIdent
import no.nav.syfo.personstatus.domain.PersonOversiktStatus
import no.nav.syfo.personstatus.domain.SearchQuery
import no.nav.syfo.personstatus.domain.Search
import no.nav.syfo.personstatus.domain.VeilederBrukerKnytning

interface IPersonOversiktStatusRepository {
Expand Down Expand Up @@ -38,7 +38,7 @@ interface IPersonOversiktStatusRepository {

fun updatePersonstatusesWithNavnAndFodselsdato(personer: List<PersonOversiktStatus>): List<Result<PersonOversiktStatus>>

fun searchPerson(searchQuery: SearchQuery): List<PersonOversiktStatus>
fun searchPerson(search: Search): List<PersonOversiktStatus>

fun updatePersonOversiktStatusOppfolgingstilfelle(
personstatus: PersonOversiktStatus,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package no.nav.syfo.personstatus.application

import no.nav.syfo.personstatus.domain.PersonOversiktStatus
import no.nav.syfo.personstatus.domain.SearchQuery
import no.nav.syfo.personstatus.domain.Search
import org.slf4j.LoggerFactory
import kotlin.jvm.java
import kotlin.time.measureTimedValue

class PersonoversiktSearchService(
private val personoversiktStatusRepository: IPersonOversiktStatusRepository,
) {
fun searchSykmeldt(searchQuery: SearchQuery): List<PersonOversiktStatus> {
fun searchSykmeldt(search: Search): List<PersonOversiktStatus> {
val (searchResult, duration) = measureTimedValue {
personoversiktStatusRepository.searchPerson(searchQuery = searchQuery)
personoversiktStatusRepository.searchPerson(search = search)
}

log.info("Completed search for sykmeldt in repository, got ${searchResult.size} results in ${duration.inWholeMilliseconds} ms")
Expand Down
24 changes: 24 additions & 0 deletions src/main/kotlin/no/nav/syfo/personstatus/domain/Search.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package no.nav.syfo.personstatus.domain

import java.time.LocalDate

sealed class Search {
data class ByName(val name: Name) : Search()
data class ByNameAndDate(val name: Name, val birthdate: LocalDate) : Search()
data class ByDate(val birthdate: LocalDate) : Search()
data class ByInitialsAndDate(val initials: Initials, val birthdate: LocalDate) : Search()
}

@JvmInline
value class Initials(val value: String) {
init {
require(value.length > 1) { "Initials must be more than one characters long" }
}
}

@JvmInline
value class Name(val value: String) {
init {
require(value.split(" ").size > 1) { "Name must consist of two continuous strings or more" }
}
}
15 changes: 0 additions & 15 deletions src/main/kotlin/no/nav/syfo/personstatus/domain/SearchQuery.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class VeilederTilgangskontrollClient(
}

suspend fun veilederPersonAccessListMedOBO(
personIdentNumberList: List<String>,
personidenter: List<String>,
token: String,
callId: String,
): List<String>? {
Expand All @@ -85,7 +85,7 @@ class VeilederTilgangskontrollClient(
header(NAV_CALL_ID_HEADER, callId)
accept(ContentType.Application.Json)
contentType(ContentType.Application.Json)
setBody(personIdentNumberList)
setBody(personidenter)
}

requestTimer.stop(HISTOGRAM_ISTILGANGSKONTROLL_PERSONER)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -303,23 +303,62 @@ class PersonOversiktStatusRepository(private val database: DatabaseInterface) :
Result.failure(e)
}

override fun searchPerson(searchQuery: SearchQuery): List<PersonOversiktStatus> {
val initials = searchQuery.initials?.value?.toList() ?: emptyList()
val baseQuery =
"SELECT * FROM PERSON_OVERSIKT_STATUS p WHERE (p.oppfolgingstilfelle_end + INTERVAL '16 DAY' >= now() OR $AKTIV_OPPGAVE_WHERE_CLAUSE) AND p.fodselsdato = ? "
val nameQuery = if (initials.isNotEmpty()) {
"AND p.name ILIKE ? "
} else ""
val orderBy = "ORDER BY name ASC"
val initialsSearchString = initials.joinToString(separator = "% ", postfix = "%")
override fun searchPerson(search: Search): List<PersonOversiktStatus> {
val results = when (search) {
is Search.ByName -> searchByName(search)
is Search.ByNameAndDate -> searchByDateAndName(search)
is Search.ByDate -> searchByDate(search)
is Search.ByInitialsAndDate -> searchByInitialsAndDate(search)
}
return results.map { it.toPersonOversiktStatus() }
}

private fun searchByName(search: Search.ByName): List<PPersonOversiktStatus> {
val name = search.name.value.split(" ").joinToString(separator = "% ", postfix = "%")
val nameQuery = "AND p.name ILIKE ?"
val query = "$SEARCH_PERSON_BASE_QUERY $nameQuery $ORDER_BY_ASC"
return database.connection.use { connection ->
connection.prepareStatement(baseQuery + nameQuery + orderBy).use {
it.setDate(1, Date.valueOf(searchQuery.birthdate))
if (initials.isNotEmpty()) {
it.setString(2, initialsSearchString)
}
connection.prepareStatement(query).use {
it.setString(1, name)
it.executeQuery().toList { toPPersonOversiktStatus() }
}.map { it.toPersonOversiktStatus() }
}
}
}

private fun searchByDateAndName(search: Search.ByNameAndDate): List<PPersonOversiktStatus> {
val name = search.name.value.split(" ").joinToString(separator = "% ", postfix = "%")
val nameAndDateQuery = "AND p.name ILIKE ? AND p.fodselsdato = ?"
val query = "$SEARCH_PERSON_BASE_QUERY $nameAndDateQuery $ORDER_BY_ASC"
return database.connection.use { connection ->
connection.prepareStatement(query).use {
it.setString(1, name)
it.setDate(2, Date.valueOf(search.birthdate))
it.executeQuery().toList { toPPersonOversiktStatus() }
}
}
}

private fun searchByDate(search: Search.ByDate): List<PPersonOversiktStatus> {
val dateQuery = "AND p.fodselsdato = ?"
val query = "$SEARCH_PERSON_BASE_QUERY $dateQuery $ORDER_BY_ASC"
return database.connection.use { connection ->
connection.prepareStatement(query).use {
it.setDate(1, Date.valueOf(search.birthdate))
it.executeQuery().toList { toPPersonOversiktStatus() }
}
}
}

private fun searchByInitialsAndDate(search: Search.ByInitialsAndDate): List<PPersonOversiktStatus> {
val initials = search.initials.value.toList().joinToString(separator = "% ", postfix = "%")
val initialsAndDateQuery = "AND p.name ILIKE ? AND p.fodselsdato = ?"
val query = "$SEARCH_PERSON_BASE_QUERY $initialsAndDateQuery $ORDER_BY_ASC"
return database.connection.use { connection ->
connection.prepareStatement(query).use {
it.setString(1, initials)
it.setDate(2, Date.valueOf(search.birthdate))
it.executeQuery().toList { toPPersonOversiktStatus() }
}
}
}

Expand Down Expand Up @@ -524,6 +563,11 @@ class PersonOversiktStatusRepository(private val database: DatabaseInterface) :
WHERE fnr = ?
RETURNING id
"""

private const val SEARCH_PERSON_BASE_QUERY =
"SELECT * FROM PERSON_OVERSIKT_STATUS p WHERE (p.oppfolgingstilfelle_end + INTERVAL '16 DAY' >= now() OR $AKTIV_OPPGAVE_WHERE_CLAUSE)"

private const val ORDER_BY_ASC = "ORDER BY name ASC"
}
}

Expand Down
Loading

0 comments on commit c709042

Please sign in to comment.