diff --git a/ovara-suoritusrekisteri/README.md b/ovara-suoritusrekisteri/README.md index 0128e4094e..57e13c7dfb 100644 --- a/ovara-suoritusrekisteri/README.md +++ b/ovara-suoritusrekisteri/README.md @@ -12,8 +12,10 @@ Muodostaa erilliset siirtotiedostot aikaikkunassa muuttuneille tiedoille: Jos muuttuneita tietoja on paljon (konffiarvo suoritusrekisteri.ovara.pagesize), muodostuu useita tiedostoja per tyyppi. -Lisäksi kerran vuorokaudessa muodostetaan siirtotiedostot kaikkien aktiivisten kk-hakujen päätellyille -ensikertalaisuustiedoille, jokainen haku erilliseen tiedostoon. +Lisäksi kerran vuorokaudessa muodostetaan siirtotiedostot kaikkien seuraaville, jokainen haku tarpeen mukaan useaan erilliseen tiedostoon: +-aktiivisten hakujen päätellyt ensikertalaisuustiedot +-aktiivisten toisen asteen hakujen proxysuoritustiedot valintalaskentakoostepalvelusta +-aktiivisten toisen asteen yhteishakujen päätellyt harkinnanvaraisuustiedot valintalaskentakoostepalvelusta Muodostetut tiedostot tallennetaan sovellukselle konffattuun s3-ämpäriin seuraavien konffiarvojen perusteella: suoritusrekisteri.ovara.s3.region diff --git a/ovara-suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/ajastus/SiirtotiedostoApp.scala b/ovara-suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/ajastus/SiirtotiedostoApp.scala index a5f3b14094..01a7b51828 100644 --- a/ovara-suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/ajastus/SiirtotiedostoApp.scala +++ b/ovara-suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/ajastus/SiirtotiedostoApp.scala @@ -54,7 +54,9 @@ object SiirtotiedostoApp { new SiirtotiedostoClientImpl(config.siirtotiedostoClientConfig), ensikertalainenActor, ovaraIntegrations.haut, - config.siirtotiedostoPageSize + config.siirtotiedostoPageSize, + ovaraIntegrations.hakemusService, + ovaraIntegrations.koosteService ) } diff --git a/suoritusrekisteri/src/main/resources/suoritusrekisteri-oph.properties b/suoritusrekisteri/src/main/resources/suoritusrekisteri-oph.properties index 44eb18b55a..416424790c 100644 --- a/suoritusrekisteri/src/main/resources/suoritusrekisteri-oph.properties +++ b/suoritusrekisteri/src/main/resources/suoritusrekisteri-oph.properties @@ -25,7 +25,7 @@ valinta-tulos-service.haku=/valinta-tulos-service/haku/$1 kouta-internal.haku.search.all=/kouta-internal/haku/search kouta-internal.hakukohde=/kouta-internal/hakukohde/$1 kouta-internal.hakukohde.batch=/kouta-internal/hakukohde/findbyoids -kouta-internal.hakukohde.search=/kouta-internal/hakukohde/search?hakuOid=$1 +kouta-internal.hakukohde.search=/kouta-internal/hakukohde/search?haku=$1 kouta-internal.toteutus=/kouta-internal/toteutus/$1 kouta-internal.koulutus=/kouta-internal/koulutus/$1 kouta-internal.haku=/kouta-internal/haku/$1 @@ -49,6 +49,7 @@ suoritusrekisteri.it.postgres.port=55432 valintalaskentakoostepalvelu.suorituksetByOpiskelijaOid=/valintalaskentakoostepalvelu/resources/proxy/suoritukset/suorituksetByOpiskelijaOid/hakuOid/$1?fetchEnsikertalaisuus=false valintalaskentakoostepalvelu.atarusuorituksetByOpiskelijaOid=/valintalaskentakoostepalvelu/resources/proxy/suoritukset/ataruSuorituksetByOpiskelijaOid/hakuOid/$1?fetchEnsikertalaisuus=false&shouldUseApplicationPersonOid=true valintalaskentakoostepalvelu.harkinnanvaraisuudet.atarutiedoille=/valintalaskentakoostepalvelu/resources/harkinnanvaraisuus/atarutiedoille +valintalaskentakoostepalvelu.harkinnanvaraisuudet.hakemuksille=/valintalaskentakoostepalvelu/resources/harkinnanvaraisuus/hakemuksille valintalaskenta-service.bypersonoid=/valintalaskenta-laskenta-service/resources/valintakoe/hakijat valintaperusteet.valintatapajonosByOids=/valintaperusteet-service/resources/valintatapajono koski.oppija=/koski/api/oppija diff --git a/suoritusrekisteri/src/main/scala/ScalatraBootstrap.scala b/suoritusrekisteri/src/main/scala/ScalatraBootstrap.scala index a3efb455e4..b3e3d92b02 100644 --- a/suoritusrekisteri/src/main/scala/ScalatraBootstrap.scala +++ b/suoritusrekisteri/src/main/scala/ScalatraBootstrap.scala @@ -162,7 +162,9 @@ class ScalatraBootstrap extends LifeCycle { new SiirtotiedostoClientImpl(config.siirtotiedostoClientConfig), koosteet.ensikertalainen, integrations.haut, - config.siirtotiedostoPageSize + config.siirtotiedostoPageSize, + integrations.hakemusService, + integrations.koosteService ) ), ("/rest/v1/hakijat", "rest/v1/hakijat") -> new HakijaResource(koosteet.hakijat), diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/hakemus/HakemusService.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/hakemus/HakemusService.scala index eeabf0b594..9a0bc28c34 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/hakemus/HakemusService.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/hakemus/HakemusService.scala @@ -131,42 +131,62 @@ object ListFullSearchDto { trait IHakemusService { def hakemuksetForPerson(personOid: String): Future[Seq[HakijaHakemus]] + def hakemuksetForPersons(personOids: Set[String]): Future[Seq[HakijaHakemus]] + def personOidstoMasterOids(personOids: Set[String]): Future[Map[String, String]] + def hakemuksetForHakukohde( hakukohdeOid: String, organisaatio: Option[String] ): Future[Seq[HakijaHakemus]] + def hakemuksetForHakukohdes( hakukohdeOid: Set[String], organisaatio: Option[String] ): Future[Seq[HakijaHakemus]] + def personOidsForHaku(hakuOid: String, organisaatio: Option[String]): Future[Set[String]] + def springPersonOidsForJatkuvaHaku(hakuOid: String): Future[Set[String]] + def personOidsForHakukohde( hakukohdeOid: String, organisaatio: Option[String] ): Future[Set[String]] + def hakemuksetForHaku(hakuOid: String, organisaatio: Option[String]): Future[Seq[HakijaHakemus]] + def hakemuksetForToisenAsteenAtaruHaku( hakuOid: String, organisaatio: Option[String], hakukohdekoodi: Option[String], hakukohdeOid: Option[String] ): Future[Seq[AtaruHakemusToinenAste]] + def suoritusoikeudenTaiAiemmanTutkinnonVuosi( hakuOid: String, hakukohdeOid: Option[String] ): Future[Seq[HakijaHakemus]] + def hakemuksetForPersonsInHaku( personOids: Set[String], hakuOid: String ): Future[Seq[HakijaHakemus]] + def addTrigger(trigger: Trigger): Unit + def reprocessHaunHakemukset(hakuOid: String): Unit + def hetuAndPersonOidForHaku(hakuOid: String): Future[Seq[HetuPersonOid]] + def hetuAndPersonOidForHakuLite(hakuOid: String): Future[Seq[HetuPersonOid]] + def hetuAndPersonOidForPersonOid(personOid: String): Future[Seq[HakemusHakuHetuPersonOid]] + + def ataruhakemustenHenkilot( + params: AtaruHenkiloSearchParams + ): Future[List[AtaruHakemuksenHenkilotiedot]] } case class AtaruHenkiloSearchParams( @@ -511,7 +531,7 @@ class HakemusService( ) } - private def ataruhakemustenHenkilot( + override def ataruhakemustenHenkilot( params: AtaruHenkiloSearchParams ): Future[List[AtaruHakemuksenHenkilotiedot]] = { val p = params.hakuOid.fold[Map[String, Any]](Map.empty)(oid => Map("hakuOid" -> oid)) ++ @@ -1267,4 +1287,8 @@ class HakemusServiceMock extends IHakemusService { override def springPersonOidsForJatkuvaHaku(hakuOid: String): Future[Set[String]] = Future.successful(Set.empty) + override def ataruhakemustenHenkilot( + params: AtaruHenkiloSearchParams + ): Future[List[AtaruHakemuksenHenkilotiedot]] = + Future.successful(List[AtaruHakemuksenHenkilotiedot]()) } diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/kooste/KoosteService.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/kooste/KoosteService.scala index 7418d46cea..901ae0b888 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/kooste/KoosteService.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/integration/kooste/KoosteService.scala @@ -3,7 +3,6 @@ package fi.vm.sade.hakurekisteri.integration.kooste import akka.actor.ActorSystem import akka.event.Logging import fi.vm.sade.hakurekisteri.integration.hakemus.{ - AtaruHakemus, AtaruHakemusToinenAste, FullHakemus, HakemuksenHarkinnanvaraisuus, @@ -11,18 +10,30 @@ import fi.vm.sade.hakurekisteri.integration.hakemus.{ } import fi.vm.sade.hakurekisteri.integration.VirkailijaRestClient -import scala.concurrent.Future +import scala.concurrent.duration.{Duration, DurationInt} +import scala.concurrent.{Await, Future} trait IKoosteService { def getSuorituksetForAtaruhakemukset( hakuOid: String, hs: Seq[HakijaHakemus] ): Future[Map[String, Map[String, String]]] + def getProxysuorituksetForHakemusOids( + hakuOid: String, + hakemusOids: Seq[String] + ): Future[Map[String, Map[String, String]]] def getSuoritukset( hakuOid: String, hakemukset: Seq[HakijaHakemus] ): Future[Map[String, Map[String, String]]] def getHarkinnanvaraisuudet(hs: Seq[HakijaHakemus]): Future[Seq[HakemuksenHarkinnanvaraisuus]] + def getHarkinnanvaraisuudetForHakemusOidsWithTimeout( + hakemusOids: Seq[String], + timeout: Duration + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] + def getHarkinnanvaraisuudetForHakemusOids( + hakemusOids: Seq[String] + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] } class KoosteService(restClient: VirkailijaRestClient, pageSize: Int = 200)(implicit @@ -59,17 +70,62 @@ class KoosteService(restClient: VirkailijaRestClient, pageSize: Int = 200)(impli } } + def getHarkinnanvaraisuudetForHakemusOidsWithTimeout( + hakemusOids: Seq[String], + timeout: Duration + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] = { + try { + logger.info( + s"${Thread.currentThread().getName} Haetaan koostepalvelusta harkinnanvaraisuudet ${hakemusOids.size} hakemukselle, timeout $timeout" + ) + Future.successful(Await.result(getHarkinnanvaraisuudetForHakemusOids(hakemusOids), timeout)) + } catch { + case e: Exception => + logger.info( + s"${Thread.currentThread().getName} harkinnanvaraisuus fetch timed out for ${hakemusOids.size}, $timeout" + ) + Future.failed(e) + } + } + + def getHarkinnanvaraisuudetForHakemusOids( + hakemusOids: Seq[String] + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] = { + logger.info( + s"${Thread.currentThread().getName} Haetaan koostepalvelusta harkinnanvaraisuudet ${hakemusOids.size} hakemukselle" + ) + if (hakemusOids.isEmpty) { + Future.successful(Seq.empty) + } else { + restClient.postObject[Seq[String], Seq[HakemuksenHarkinnanvaraisuus]]( + "valintalaskentakoostepalvelu.harkinnanvaraisuudet.hakemuksille" + )(200, hakemusOids) + } + } + def getSuorituksetForAtaruhakemukset( hakuOid: String, hs: Seq[HakijaHakemus] ): Future[Map[String, Map[String, String]]] = { - val hakemusOids = hs.map(hh => hh.oid).toList + if (hakemusOids.nonEmpty) { + getProxysuorituksetForHakemusOids(hakuOid, hakemusOids) + } else { + logger.debug(s"No ataruhakemukses found!") + Future.successful(Map.empty) + } + } + + def getProxysuorituksetForHakemusOids( + hakuOid: String, + hakemusOids: Seq[String] + ): Future[Map[String, Map[String, String]]] = { + logger.info( - s"Getting atarusuoritukset from koostepalvelu for ataruhakemukset: ${hakemusOids}" + s"${Thread.currentThread().getName} Getting atarusuoritukset from koostepalvelu for ${hakemusOids.size} ataruhakemukses in haku $hakuOid" ) if (hakemusOids.nonEmpty) { - restClient.postObject[List[String], Map[String, Map[String, String]]]( + restClient.postObject[Seq[String], Map[String, Map[String, String]]]( "valintalaskentakoostepalvelu.atarusuorituksetByOpiskelijaOid", hakuOid )(200, hakemusOids) @@ -77,7 +133,6 @@ class KoosteService(restClient: VirkailijaRestClient, pageSize: Int = 200)(impli logger.info(s"No ataruhakemukses found!") Future.successful(Map.empty) } - } def getSuoritukset( @@ -110,6 +165,7 @@ class KoosteService(restClient: VirkailijaRestClient, pageSize: Int = 200)(impli } case class HakemusHakija(opiskelijaOid: String, hakemus: FullHakemus) + } class KoosteServiceMock extends IKoosteService { @@ -128,4 +184,22 @@ class KoosteServiceMock extends IKoosteService { hakemukset: Seq[HakijaHakemus] ): Future[Map[String, Map[String, String]]] = Future.successful(Map[String, Map[String, String]]()) + + override def getProxysuorituksetForHakemusOids( + hakuOid: String, + hakemusOids: Seq[String] + ): Future[Map[String, Map[String, String]]] = { + Future.successful(Map.empty) + } + + override def getHarkinnanvaraisuudetForHakemusOids( + hakemusOids: Seq[String] + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] = { + Future.successful(Seq.empty) + } + + override def getHarkinnanvaraisuudetForHakemusOidsWithTimeout( + hakemusOids: Seq[String], + timeout: Duration + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] = ??? } diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraDbRepository.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraDbRepository.scala index 06365992c7..1db845b234 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraDbRepository.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraDbRepository.scala @@ -27,7 +27,9 @@ trait OvaraDbRepository { } -case class SiirtotiedostoProcessInfo(entityTotals: Map[String, Long]) +case class SiirtotiedostoProcessInfo( + entityTotals: Map[String, String] = Map.empty +) case class SiirtotiedostoProcess( id: Long, diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraResource.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraResource.scala index 6c4f984257..c6a9557e5f 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraResource.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraResource.scala @@ -15,21 +15,26 @@ import org.json4s._ import org.json4s.jackson.Serialization.write import org.scalatra.{SessionSupport, _} import org.scalatra.json.{JValueResult, JacksonJsonSupport} +import org.scalatra.swagger.{Swagger, SwaggerEngine, SwaggerSupport} import org.slf4j.LoggerFactory import java.util.UUID import scala.util.Try -class OvaraResource(ovaraService: OvaraService)(implicit val security: Security) +class OvaraResource(ovaraService: OvaraService)(implicit val security: Security, sw: Swagger) extends ScalatraServlet with JValueResult with JacksonJsonSupport with SessionSupport with SecuritySupport - with Logging { + with Logging + with OvaraSwaggerApi { val audit: Audit = SuoritusAuditVirkailija.audit - get("/muodosta") { + protected implicit def swagger: SwaggerEngine[_] = sw + override protected def applicationDescription: String = "Ovara-Resource" + + get("/muodosta", operation(muodostaAikavalille)) { if (currentUser.exists(_.isAdmin)) { val start = params.get("start").map(_.toLong) val end = params.get("end").map(_.toLong) @@ -62,30 +67,24 @@ class OvaraResource(ovaraService: OvaraService)(implicit val security: Security) } - get("/muodosta/ensikertalaisuudet") { - if (currentUser.exists(_.isAdmin)) { - val hakuOid = params.get("haku") - hakuOid match { - case Some(hakuOid) => - logger.info(s"Muodostetaan ensikertalaisten siirtotiedosto haulle $hakuOid") - val result = ovaraService.formEnsikertalainenSiirtotiedostoForHakus(Seq(hakuOid)) - Ok(s"$result") - case _ => - BadRequest(s"Pakollinen parametri (haku) puuttuu!") - } - } else { - Forbidden("Ei tarvittavia oikeuksia ovara-siirtotiedoston muodostamiseen") - } - } - - get("/muodosta/ensikertalaisuudet/kkhaut") { + get("/muodosta/paivittaiset", operation(muodostaPaivittaiset)) { if (currentUser.exists(_.isAdmin)) { val executionId = UUID.randomUUID().toString val vainAktiiviset: Boolean = params.get("vainAktiiviset").exists(_.toBoolean) + val ensikertalaisuudet: Boolean = params.get("ensikertalaisuudet").exists(_.toBoolean) + val harkinnanvaraisuudet: Boolean = params.get("harkinnanvaraisuudet").exists(_.toBoolean) + val proxySuoritukset: Boolean = params.get("proxySuoritukset").exists(_.toBoolean) + val combinedParams = DailyProcessingParams( + executionId, + vainAktiiviset, + ensikertalaisuudet = ensikertalaisuudet, + harkinnanvaraisuudet = harkinnanvaraisuudet, + proxySuoritukset = proxySuoritukset + ) logger.info( - s"$executionId Muodostetaan ensikertalaisten siirtotiedosto kk-hauille. Vain aktiiviset: $vainAktiiviset" + s"$executionId Muodostetaan päivittäiset siirtotiedostot. $combinedParams" ) - val result = ovaraService.triggerEnsikertalaiset(vainAktiiviset, executionId) + val result = ovaraService.triggerDailyProcessing(combinedParams) Ok(s"$executionId Valmista - $result") } else { Forbidden("Ei tarvittavia oikeuksia ovara-siirtotiedoston muodostamiseen") diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraService.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraService.scala index abb79911e5..349b7fbadb 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraService.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraService.scala @@ -9,7 +9,14 @@ import fi.vm.sade.hakurekisteri.ensikertalainen.{ MenettamisenPeruste } import fi.vm.sade.hakurekisteri.integration.ExecutorUtil +import fi.vm.sade.hakurekisteri.integration.hakemus.{ + AtaruHakemuksenHenkilotiedot, + AtaruHenkiloSearchParams, + HakemuksenHarkinnanvaraisuus, + IHakemusService +} import fi.vm.sade.hakurekisteri.integration.haku.{AllHaut, Haku, HakuRequest} +import fi.vm.sade.hakurekisteri.integration.kooste.IKoosteService import fi.vm.sade.utils.slf4j.Logging import java.util.UUID @@ -26,25 +33,37 @@ trait IOvaraService { def muodostaSeuraavaSiirtotiedosto(): SiirtotiedostoProcess def formSiirtotiedostotPaged(process: SiirtotiedostoProcess): SiirtotiedostoProcess def formEnsikertalainenSiirtotiedostoForHakus( - hakuOids: Seq[String] - ): Seq[HaunEnsikertalaisetResult] + hakuOids: Set[String] + ): Seq[SiirtotiedostoResultForHaku] } -case class HaunEnsikertalaisetResult(hakuOid: String, total: Int, error: Option[Throwable]) +case class SiirtotiedostoResultForHaku( + hakuOid: String, + tyyppi: String, + total: Int, + error: Option[Throwable] +) + +case class DailyProcessingParams( + executionId: String, + vainAktiiviset: Boolean, + ensikertalaisuudet: Boolean, + harkinnanvaraisuudet: Boolean, + proxySuoritukset: Boolean +) class OvaraService( db: OvaraDbRepository, s3Client: SiirtotiedostoClient, ensikertalainenActor: ActorRef, hakuActor: ActorRef, - pageSize: Int + pageSize: Int, + hakemusService: IHakemusService, + koosteService: IKoosteService ) extends IOvaraService with Logging { - implicit val ec: ExecutionContext = ExecutorUtil.createExecutor( - 6, - getClass.getSimpleName - ) + implicit val ec: ExecutionContext = ExecutorUtil.createExecutor(10, "ovara-executor") @tailrec private def saveInSiirtotiedostoPaged[T]( @@ -62,7 +81,13 @@ class OvaraService( s"(${params.executionId}) Saatiin sivu (${pageResults.size} kpl), haettu yhteensä ${params.offset + pageResults.size} kpl. Tallennetaan siirtotiedosto ennen seuraavan sivun hakemista. $params" ) s3Client - .saveSiirtotiedosto[T](params.tyyppi, pageResults, params.executionId, params.fileCounter) + .saveSiirtotiedosto[T]( + params.tyyppi, + pageResults, + params.executionId, + params.fileCounter, + None + ) saveInSiirtotiedostoPaged( params .copy(offset = params.offset + pageResults.size, fileCounter = params.fileCounter + 1), @@ -96,12 +121,88 @@ class OvaraService( menettamisenPeruste: Option[MenettamisenPeruste] ) - //Haetaan hakujen oidit ja synkataan ensikertalaiset näille - def triggerEnsikertalaiset( - vainAktiiviset: Boolean, - executionId: String - ): Seq[HaunEnsikertalaisetResult] = { - implicit val to: Timeout = Timeout(5.minutes) + case class SiirtotiedostoProxySuoritukset( + hakemusOid: String, + hakuOid: String, + henkiloOid: String, + values: Map[String, String] + ) + + def getHarkinnanvaraisuusResults( + executionId: String, + hakuOids: Seq[String] + ): Future[Seq[SiirtotiedostoResultForHaku]] = { + implicit val ec: ExecutionContext = ExecutorUtil.createExecutor( + 2, + "harkinnanvaraisuus-executor" + ) + val result = Future.sequence( + hakuOids.map(hakuOid => + processHakuForHarkinnanvaraisuus(executionId, hakuOid).recoverWith { case e: Exception => + logger.error( + s"${Thread.currentThread().getName} harkinnanvaraisuudet epäonnistui haulle $hakuOid", + e + ) + Future.successful( + SiirtotiedostoResultForHaku(hakuOid, "harkinnanvaraisuus", 0, Some(e)) + ) + } + ) + ) + result.onComplete(res => + logger.info(s"${Thread.currentThread().getName} Valmista (harkinnanvaraisuudet)! $res") + ) + result + } + + def getProxysuorituksetResults( + executionId: String, + hakuOids: Seq[String] + ): Future[Seq[SiirtotiedostoResultForHaku]] = { + implicit val ec: ExecutionContext = ExecutorUtil.createExecutor( + 2, + "proxysuoritukset-executor" + ) + val valmiitaProxyHakuja = new AtomicReference[Int](0) + val proxySuorituksetGroups = + hakuOids.grouped((hakuOids.size / 4) + 1).toSeq + val proxySuorituksetFutures = proxySuorituksetGroups.map(pGroup => { + pGroup.foldLeft(Future.successful(Seq[SiirtotiedostoResultForHaku]())) { + case (accResult, hakuOid) => + accResult.flatMap((r: Seq[SiirtotiedostoResultForHaku]) => { + logger.info( + s"${Thread.currentThread().getName} Valmiita proxyhakuja ${valmiitaProxyHakuja + .updateAndGet(n => n + 1)}/${hakuOids.size}" + ) + processHakuForProxysuoritukset(executionId, hakuOid) + .recoverWith { case e: Exception => + logger.error( + s"${Thread.currentThread().getName} proxysuoritukset epäonnistui haulle $hakuOid", + e + ) + Future.successful( + SiirtotiedostoResultForHaku(hakuOid, "proxysuoritukset", 0, Some(e)) + ) + } + .map(psResult => { + r :+ psResult + }) + }) + } + }) + val result = Future.sequence(proxySuorituksetFutures).map(_.flatten) + result.onComplete(res => + logger.info(s"${Thread.currentThread().getName} Valmista (proxysuoritukset)! $res") + ) + result + } + + //Kerran päivässä muodostetaan uudelleen päätellyt tiedot, joista on vaikea sanoa millä hetkellä ne tarkalleen muuttuvat koska ne yhdistelevät useiden palveluiden dataa. + //Näitä ovat ensikertalaisuudet (kk-haut), pohjakoulutus (toisen asteen haut) sekä harkinnanvaraisuus (toisen asteen yhteishaku) + def triggerDailyProcessing( + params: DailyProcessingParams + ): Seq[SiirtotiedostoResultForHaku] = { + implicit val to: Timeout = Timeout(11.minutes) val MILLIS_TO_WAIT = 5000 //Odotetaan, että HakuActor saa haut ladattua cacheen. @@ -116,50 +217,223 @@ class OvaraService( if (hakuResult.haut.nonEmpty) { hakuResult.haut } else { - logger.info(s"$executionId HakuCache ei vielä valmis, odotetaan $MILLIS_TO_WAIT ms") + logger.info( + s"${params.executionId} HakuCache ei vielä valmis, odotetaan $MILLIS_TO_WAIT ms" + ) Thread.sleep(MILLIS_TO_WAIT) waitForHautCache(millisToWaitLeft - MILLIS_TO_WAIT) } } else { - logger.error(s"$executionId Hakuja ei saatu ladattua") + logger.error(s"${params.executionId} Hakuja ei saatu ladattua") throw new RuntimeException(s"Hakuja ei saatu ladattua") } } - logger.info(s"$executionId Muodostetaan ensikertalaisuudet, vain aktiiviset: $vainAktiiviset") + logger.info(s"${params.executionId} Muodostetaan päivittäiset, params: $params") try { val haut: Seq[Haku] = waitForHautCache(600 * 1000) - val kiinnostavat = - haut.filter(haku => (!vainAktiiviset || haku.isActive) && haku.kkHaku).map(_.oid) + + //Harkinnanvaraisuudet (aktiivisille) toisen asteen yhteishauille + val hautForHarkinnanvaraisuus = + if (params.harkinnanvaraisuudet) + haut + .filter(haku => + (!params.vainAktiiviset || haku.isActive) && haku.toisenAsteenHaku && haku.hakutapaUri + .startsWith("hakutapa_01") + ) + .map(_.oid) + .toSet + else Set[String]() + + //Proxysuoritukset (aktiivisille) toisen asteen hauille + val hautForProxysuoritukset = + if (params.proxySuoritukset) + haut + .filter(haku => (!params.vainAktiiviset || haku.isActive) && haku.toisenAsteenHaku) + .map(_.oid) + .toSet + else Set[String]() + + //Ensikertalaisuudet (aktiivisille) kk-hauille + logger.info( + s"Käsitellään ${hautForHarkinnanvaraisuus.size} haun harkinnanvaraisuudet ja ${hautForProxysuoritukset.size} haun proxySuoritukset" + ) + + val hautForEnsikertalaisuus = if (params.ensikertalaisuudet) { + haut + .filter(haku => (!params.vainAktiiviset || haku.isActive) && haku.kkHaku) + .map(_.oid) + .toSet + } else Set[String]() logger.info( - s"$executionId Löydettiin ${kiinnostavat.size} kiinnostavaa hakua yhteensä ${haut.size} hausta. Vain aktiiviset: $vainAktiiviset" + s"Käsitellään ${hautForHarkinnanvaraisuus.size} haun harkinnanvaraisuudet, ${hautForProxysuoritukset.size} haun proxySuoritukset ja ${hautForEnsikertalaisuus.size} haun harkinnanvaraisuudet" + ) + + val harkinnanvaraisuusF = + getHarkinnanvaraisuusResults(params.executionId, hautForHarkinnanvaraisuus.toSeq) + val proxysuoritusF = + getProxysuorituksetResults(params.executionId, hautForProxysuoritukset.toSeq) + val ensikertalaisetResults = formEnsikertalainenSiirtotiedostoForHakus( + hautForEnsikertalaisuus ) - formEnsikertalainenSiirtotiedostoForHakus(kiinnostavat) + + val combinedResults = for { + harkinnanvaraisuusResults <- harkinnanvaraisuusF + proxysuorituksetResults <- proxysuoritusF + } yield { + harkinnanvaraisuusResults ++ proxysuorituksetResults ++ ensikertalaisetResults + } + Await.result(combinedResults, 3.hours) } catch { case t: Throwable => logger.error( - s"$executionId Ensikertalaisten siirtotiedostojen muodostaminen epäonnistui: ", + s"${params.executionId} Ensikertalaisten siirtotiedostojen muodostaminen epäonnistui: ", t ) throw t } } + val GROUP_SIZE_PROXY = 100 + def processHakuForProxysuoritukset(executionId: String, hakuOid: String)(implicit + ec: ExecutionContext + ): Future[SiirtotiedostoResultForHaku] = { + try { + val fileCounter = new AtomicReference[Int](1) + val hakijat = hakemusService + .ataruhakemustenHenkilot( + AtaruHenkiloSearchParams(hakuOid = Some(hakuOid), hakukohdeOids = None) + ) + + val countF = hakijat.flatMap(h => { + logger.info( + s"$executionId Saatiin ${h.size} hakijaa haulle $hakuOid, haetaan proxysuoritukset" + ) + val erat = h.grouped(GROUP_SIZE_PROXY).toSeq + + erat.foldLeft(Future.successful(0)) { case (accResult, chunk) => + accResult.flatMap(rs => { + koosteService + .getProxysuorituksetForHakemusOids(hakuOid, chunk.map(_.oid)) + .map(rawChunkResult => { + val personOidToHakemusOidMap = chunk.map(ht => ht.personOid.get -> ht.oid).toMap + rawChunkResult + .map(singleResult => { + SiirtotiedostoProxySuoritukset( + hakemusOid = personOidToHakemusOidMap.getOrElse( + singleResult._1, + throw new RuntimeException("personOid -> hakemusOid mapping not found!") + ), + hakuOid = hakuOid, + henkiloOid = singleResult._1, + values = singleResult._2 + ) + }) + .toSeq + }) + .map(proxySuoritukset => { + logger.info( + s"$executionId Saatiin ${proxySuoritukset.size} proxysuoritusta haun $hakuOid, hakijalle, erä ${fileCounter + .get()}/${erat.size}. Tallennetaan siirtotiedosto." + ) + s3Client + .saveSiirtotiedosto[SiirtotiedostoProxySuoritukset]( + "proxysuoritus", + proxySuoritukset, + executionId, + fileCounter.getAndUpdate(n => n + 1), + Some(hakuOid) + ) + rs + proxySuoritukset.size + }) + }) + } + }) + countF.map(count => SiirtotiedostoResultForHaku(hakuOid, "proxysuoritukset", count, None)) + } catch { + case t: Throwable => + logger.error(s"$executionId Jotain meni yllättävän paljon vikaan", t) + Future.successful(SiirtotiedostoResultForHaku(hakuOid, "proxysuoritukset", 0, Some(t))) + } + } + + val GROUP_SIZE = + 100 //Tämä on varmaan naurettavan pieni, mutta suoritusaika vaihtelee valtavasti ja suuremmista määristä seuraa timeouteja. + def processHakuForHarkinnanvaraisuus(executionId: String, hakuOid: String)(implicit + ec: ExecutionContext + ): Future[SiirtotiedostoResultForHaku] = { + try { + val fileCounter = new AtomicReference[Int](1) + val hakijat = hakemusService + .ataruhakemustenHenkilot( + AtaruHenkiloSearchParams(hakuOid = Some(hakuOid), hakukohdeOids = None) + ) + + val countF = hakijat.flatMap(h => { + logger.info( + s"${Thread.currentThread().getName} $executionId Saatiin ${h.size} hakijaa haulle $hakuOid, haetaan harkinnanvaraisuudet. " + ) + val erat = h.grouped(GROUP_SIZE).toSeq + + erat.foldLeft(Future.successful(0)) { case (accResult, chunk) => + accResult.flatMap(rs => { + koosteService + .getHarkinnanvaraisuudetForHakemusOidsWithTimeout(chunk.map(_.oid), 5.minutes) + .recoverWith { case e: Exception => + logger.error( + s"${Thread.currentThread().getName} erä ${fileCounter.get()}/${erat.size} harkinnanvaraisuudet epäonnistui haulle $hakuOid, yritetään kerran uudelleen", + e + ) + koosteService + .getHarkinnanvaraisuudetForHakemusOidsWithTimeout(chunk.map(_.oid), 5.minutes) + } + .map(harkinnanvaraisuudet => { + logger.info( + s"${Thread.currentThread().getName} $executionId Saatiin ${harkinnanvaraisuudet.size} harkinnanvaraisuutta haun $hakuOid, hakijalle, erä ${fileCounter + .get()}/${erat.size}. Tallennetaan siirtotiedosto." + ) + s3Client + .saveSiirtotiedosto[HakemuksenHarkinnanvaraisuus]( + "harkinnanvaraisuus", + harkinnanvaraisuudet, + executionId, + fileCounter.getAndUpdate(n => n + 1), + Some(hakuOid) + ) + rs + harkinnanvaraisuudet.size + }) + }) + } + }) + countF.map(count => SiirtotiedostoResultForHaku(hakuOid, "harkinnanvaraisuus", count, None)) + } catch { + case t: Throwable => + logger.error(s"${Thread.currentThread().getName} Jotain meni yllättävän paljon vikaan", t) + Future.successful(SiirtotiedostoResultForHaku(hakuOid, "harkinnanvaraisuus", 0, Some(t))) + } + } + //Ensivaiheessa ajetaan tämä kaikille kk-hauille kerran, myöhemmin riittää synkata kerran päivässä aktiivisten kk-hakujen tiedot def formEnsikertalainenSiirtotiedostoForHakus( - hakuOids: Seq[String] - ): Seq[HaunEnsikertalaisetResult] = { + hakuOids: Set[String] + ): Seq[SiirtotiedostoResultForHaku] = { + implicit val ec: ExecutionContext = ExecutorUtil.createExecutor( + 4, + "ensikertalaisuudet-executor" + ) val executionId = UUID.randomUUID().toString val fileCounter = new AtomicReference[Int](0) - val results = new AtomicReference[List[HaunEnsikertalaisetResult]](List.empty) + val results = new AtomicReference[List[SiirtotiedostoResultForHaku]](List.empty) def formEnsikertalainenSiirtotiedostoForHaku(hakuOid: String): Future[(String, Int)] = { implicit val to: Timeout = Timeout(30.minutes) - logger.info(s"($executionId) Ei löytynyt lainkaan ensikertalaisuustietoja haulle $hakuOid") + logger.info( + s"${Thread.currentThread().getName} $executionId Ei löytynyt lainkaan ensikertalaisuustietoja haulle $hakuOid" + ) val ensikertalaiset: Future[Seq[Ensikertalainen]] = (ensikertalainenActor ? HaunEnsikertalaisetQuery(hakuOid)).mapTo[Seq[Ensikertalainen]] ensikertalaiset.map((rawEnsikertalaiset: Seq[Ensikertalainen]) => { logger.info( - s"($executionId) Saatiin ${rawEnsikertalaiset.size} ensikertalaisuustietoa haulle $hakuOid. Tallennetaan siirtotiedosto." + s"${Thread.currentThread().getName} $executionId Saatiin ${rawEnsikertalaiset.size} ensikertalaisuustietoa haulle $hakuOid. Tallennetaan siirtotiedosto." ) val ensikertalaiset = rawEnsikertalaiset.map(e => SiirtotiedostoEnsikertalainen( @@ -180,7 +454,7 @@ class OvaraService( (hakuOid, ensikertalaiset.size) } else { logger.info( - s"($executionId) Ei löytynyt lainkaan ensikertalaisuustietoja haulle $hakuOid" + s"${Thread.currentThread().getName} ($executionId) Ei löytynyt lainkaan ensikertalaisuustietoja haulle $hakuOid" ) (hakuOid, 0) } @@ -196,25 +470,31 @@ class OvaraService( try { val result = Await.result(formEnsikertalainenSiirtotiedostoForHaku(hakuOid), 45.minutes) logger.info( - s"($executionId) Valmista haulle $hakuOid, kesto ${System.currentTimeMillis() - start} ms" + s"${Thread.currentThread().getName} ($executionId) Valmista haulle $hakuOid, kesto ${System + .currentTimeMillis() - start} ms" ) val totalProcessed = - results.updateAndGet(r => HaunEnsikertalaisetResult(result._1, result._2, None) :: r) + results.updateAndGet(r => + SiirtotiedostoResultForHaku(result._1, "ensikertalaisuus", result._2, None) :: r + ) logger.info( - s"Valmiina ${totalProcessed.size} / ${hakuOids.size}, onnistuneita ${totalProcessed + s"${Thread.currentThread().getName} Valmiina ${totalProcessed.size} / ${hakuOids.size}, onnistuneita ${totalProcessed .count(_.error.isEmpty)} ja epäonnistuneita ${totalProcessed.count(_.error.nonEmpty)}" ) } catch { case t: Throwable => logger .error( - s"($executionId) (kesto ${System.currentTimeMillis() - start} ms) Siirtotiedoston muodostaminen haun $hakuOid ensikertalaisista epäonnistui:", + s"${Thread.currentThread().getName} ($executionId) (kesto ${System + .currentTimeMillis() - start} ms) Siirtotiedoston muodostaminen haun $hakuOid ensikertalaisista epäonnistui:", t ) val totalProcessed = - results.updateAndGet(r => HaunEnsikertalaisetResult(hakuOid, 0, Some(t)) :: r) + results.updateAndGet(r => + SiirtotiedostoResultForHaku(hakuOid, "ensikertalaisuus", 0, Some(t)) :: r + ) logger.info( - s"Valmiina ${totalProcessed.size} / ${hakuOids.size}, onnistuneita ${totalProcessed + s"${Thread.currentThread().getName} $executionId Valmiina ${totalProcessed.size} / ${hakuOids.size}, onnistuneita ${totalProcessed .count(_.error.isEmpty)} ja epäonnistuneita ${totalProcessed.count(_.error.nonEmpty)}" ) } @@ -223,10 +503,12 @@ class OvaraService( val failed = finalResults.filter(_.error.isDefined) failed.foreach(result => logger.error( - s"Ei saatu muodostettua ensikertalaisten siirtotiedostoa haulle ${result.hakuOid}: ${result.error}" + s"${Thread.currentThread().getName} Ei saatu muodostettua ensikertalaisten siirtotiedostoa haulle ${result.hakuOid}: ${result.error}" ) ) - logger.info(s"Onnistuneita ${hakuOids.size - failed.size}, epäonnistuneita ${failed.size}") + logger.info( + s"${Thread.currentThread().getName} Ensikertalaisuudet muodostettu! Onnistuneita hakuja ${hakuOids.size - failed.size}, epäonnistuneita ${failed.size}" + ) finalResults } @@ -257,16 +539,30 @@ class OvaraService( newProcessInfo ) - val ensikertalaisetResults = + val dailyResults: Seq[SiirtotiedostoResultForHaku] = if (formEnsikertalaisuudet) { - logger.info(s"$executionId Muo") - triggerEnsikertalaiset(true, executionId) + logger.info(s"$executionId Muodostetaan päivittäiset tiedostot") + triggerDailyProcessing( + DailyProcessingParams( + executionId, + vainAktiiviset = true, + ensikertalaisuudet = true, + harkinnanvaraisuudet = true, + proxySuoritukset = true + ) + ) } else Seq.empty val combinedInfo = SiirtotiedostoProcessInfo( - mainResults.info.entityTotals ++ ensikertalaisetResults - .map(r => "ek_" + r.hakuOid -> r.total.toLong) - .toMap + entityTotals = mainResults.info.entityTotals + ++ + dailyResults + .map(result => + result.hakuOid + "_" + result.tyyppi -> result.error + .map(_.getMessage) + .getOrElse(result.total.toString) + ) + .toMap ) val combinedSuccessfulResults = mainResults.copy( @@ -323,13 +619,16 @@ class OvaraService( params => db.getChangedOpiskeluoikeudet(params) ).fold(t => throw t, c => c) val resultCounts = Map( - "suoritus" -> suoritusResult, - "arvosana" -> arvosanaResult, - "opiskelija" -> opiskelijaResult, - "opiskeluoikeus" -> opiskeluoikeusResult + "suoritus" -> suoritusResult.toString, + "arvosana" -> arvosanaResult.toString, + "opiskelija" -> opiskelijaResult.toString, + "opiskeluoikeus" -> opiskeluoikeusResult.toString ) logger.info(s"(${process.executionId}) Siirtotiedostot muodostettu, tuloksia: $resultCounts") - process.copy(info = SiirtotiedostoProcessInfo(resultCounts), finishedSuccessfully = true) + process.copy( + info = SiirtotiedostoProcessInfo(resultCounts), + finishedSuccessfully = true + ) } } @@ -338,8 +637,8 @@ class OvaraServiceMock extends IOvaraService { override def formSiirtotiedostotPaged(process: SiirtotiedostoProcess) = ??? override def formEnsikertalainenSiirtotiedostoForHakus( - hakuOids: Seq[String] - ): Seq[HaunEnsikertalaisetResult] = ??? + hakuOids: Set[String] + ): Seq[SiirtotiedostoResultForHaku] = ??? override def muodostaSeuraavaSiirtotiedosto(): SiirtotiedostoProcess = ??? } diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraSwaggerApi.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraSwaggerApi.scala new file mode 100644 index 0000000000..5c9bc20f9a --- /dev/null +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/OvaraSwaggerApi.scala @@ -0,0 +1,46 @@ +package fi.vm.sade.hakurekisteri.ovara + +import org.scalatra.swagger.SwaggerSupport + +trait OvaraSwaggerApi extends SwaggerSupport { + + val muodostaAikavalille = apiOperation[Any]("muodostaSiirtotiedostoAikavalille") + .summary("Muodostaa siirtotiedostot aikavälillä muuttuneista tiedostoista.") + .description("Muodostaa siirtotiedostot aikavälillä muuttuneista tiedostoista.") + .parameter( + pathParam[Long]("start") + .description("Aikavälin alkuhetki, esim 1731537749666") + .defaultValue(1731587749666L) + ) + .parameter( + pathParam[Long]("end") + .description("Aikavälin loppuhetki, esim 1731587968107L") + .defaultValue(1731587968107L) + ) + .tags("Ovara-resource") + + val muodostaPaivittaiset = apiOperation[Any]("muodostaPaivittaisetPaatellytSiirtotiedostot") + .summary( + "Muodostaa ovara-siirtotiedostot relevanttien hakujen ensikertalaisuuksille, proxysuoritustiedoille ja harkinnanvaraisuuksille." + ) + .description( + "Muodostaa ovara-siirtotiedostot relevanttien hakujen ensikertalaisuuksille, proxysuoritustiedoille ja harkinnanvaraisuuksille." + ) + .parameter( + pathParam[Boolean]("vainAktiiviset") + .description("Käsitelläänkö vain aktiiviset haut") + ) + .parameter( + pathParam[Boolean]("ensikertalaisuudet") + .description("Muodostetaanko ensikertalaisuudet") + ) + .parameter( + pathParam[Boolean]("harkinnanvaraisuudet") + .description("Muodostetaanko harkinnanvaraisuudet") + ) + .parameter( + pathParam[Boolean]("proxySuoritukset") + .description("Muodostetaanko proxySuoritukset") + ) + .tags("Ovara-resource") +} diff --git a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/SiirtotiedostoClientImpl.scala b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/SiirtotiedostoClientImpl.scala index 422f2a6516..08c47588bf 100644 --- a/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/SiirtotiedostoClientImpl.scala +++ b/suoritusrekisteri/src/main/scala/fi/vm/sade/hakurekisteri/ovara/SiirtotiedostoClientImpl.scala @@ -15,7 +15,8 @@ trait SiirtotiedostoClient { contentType: String, content: Seq[T], executionId: String, - fileNumber: Int + fileNumber: Int, + additionalInfo: Option[String] = None ): Unit } @@ -33,7 +34,8 @@ class SiirtotiedostoClientImpl(config: SiirtotiedostoClientConfig) contentType: String, content: Seq[T], executionId: String, - fileNumber: Int + fileNumber: Int, + additionalInfo: Option[String] = None ): Unit = { try { if (content.nonEmpty) { @@ -69,7 +71,8 @@ class MockSiirtotiedostoClient() extends SiirtotiedostoClient with Logging { contentType: String, content: Seq[T], executionId: String, - fileNumber: Int + fileNumber: Int, + additionalInfo: Option[String] = None ): Unit = { logger.info( s"($executionId) Saving siirtotiedosto... total ${content.length}, first: ${content.head}" diff --git a/suoritusrekisteri/src/main/scala/support/Integrations.scala b/suoritusrekisteri/src/main/scala/support/Integrations.scala index 2ac7897167..5a348c7032 100644 --- a/suoritusrekisteri/src/main/scala/support/Integrations.scala +++ b/suoritusrekisteri/src/main/scala/support/Integrations.scala @@ -127,6 +127,7 @@ trait OvaraIntegrations { val haut: ActorRef val valintarekisteri: ValintarekisteriActorRef val hakemusService: IHakemusService + val koosteService: IKoosteService val oppijaNumeroRekisteri: IOppijaNumeroRekisteri } @@ -259,6 +260,7 @@ class BaseIntegrations(rekisterit: Registers, system: ActorSystem, config: Confi private val logger = LoggerFactory.getLogger(getClass) logger.info(s"Initializing BaseIntegrations started...") val restEc = ExecutorUtil.createExecutor(10, "rest-client-pool") + val koosteEc = ExecutorUtil.createExecutor(10, "kooste-client-pool") val laskentaEc = ExecutorUtil.createExecutor(10, "valintalaskenta-client-pool") val pisteEc = ExecutorUtil.createExecutor(10, "pistesyotto-client-pool") val vtsEc = ExecutorUtil.createExecutor(5, "valinta-tulos-client-pool") @@ -269,6 +271,7 @@ class BaseIntegrations(rekisterit: Registers, system: ActorSystem, config: Confi system.registerOnTermination(() => { restEc.shutdown() + koosteEc.shutdown() vtsEc.shutdown() laskentaEc.shutdown() pisteEc.shutdown() @@ -309,7 +312,7 @@ class BaseIntegrations(rekisterit: Registers, system: ActorSystem, config: Confi serviceUrlSuffix = "/auth/cas" )(restEc, system) private val koosteClient = - new VirkailijaRestClient(config.integrations.koosteConfig, None)(restEc, system) + new VirkailijaRestClient(config.integrations.koosteConfig, None)(koosteEc, system) private val parametritClient = new VirkailijaRestClient(config.integrations.parameterConfig, None)(restEc, system) private val valintatulosClient = @@ -696,7 +699,9 @@ class OvaraBaseIntegrations(system: ActorSystem, config: Config) extends OvaraIn ), name ) - + private val koosteClient = + new VirkailijaRestClient(config.integrations.koosteConfig, None)(restEc, system) + val koosteService = new KoosteService(koosteClient)(system) val cacheFactory = new InMemoryCacheFactory val koodisto = new KoodistoActorRef( diff --git a/suoritusrekisteri/src/test/resources/suoritusrekisteri-oph.properties b/suoritusrekisteri/src/test/resources/suoritusrekisteri-oph.properties index cbfc3455a7..c262394c3e 100644 --- a/suoritusrekisteri/src/test/resources/suoritusrekisteri-oph.properties +++ b/suoritusrekisteri/src/test/resources/suoritusrekisteri-oph.properties @@ -25,13 +25,14 @@ valinta-tulos-service.haku=/valinta-tulos-service/haku/$1 kouta-internal.haku.search.all=/kouta-internal/haku/search kouta-internal.hakukohde=/kouta-internal/hakukohde/$1 kouta-internal.hakukohde.batch=/kouta-internal/hakukohde/findbyoids -kouta-internal.hakukohde.search=/kouta-internal/hakukohde/search?hakuOid=$1 +kouta-internal.hakukohde.search=/kouta-internal/hakukohde/search?haku=$1 kouta-internal.toteutus=/kouta-internal/toteutus/$1 kouta-internal.koulutus=/kouta-internal/koulutus/$1 haku-app.listfull=/haku-app/applications/listfull haku-app.hakemus=/haku-app/virkailija/hakemus/$1/ ataru.hakemus=/lomake-editori/applications/search?term=$1 ataru.applications=/lomake-editori/api/external/suoritusrekisteri +ataru.applications.henkilotiedot=/lomake-editori/api/external/suoritusrekisteri/henkilot ataru.applications.toinenaste=/lomake-editori/api/external/suoritusrekisteri/haku/$1/toinenaste ataru.permissioncheck=/lomake-editori/api/checkpermission pistesyotto-service.hakemuksen.pisteet=/valintapiste-service/api/pisteet-with-hakemusoids @@ -45,6 +46,7 @@ ytl.http.password=ytlpassword valintalaskentakoostepalvelu.suorituksetByOpiskelijaOid=/valintalaskentakoostepalvelu/resources/proxy/suoritukset/suorituksetByOpiskelijaOid/hakuOid/$1?fetchEnsikertalaisuus=false valintalaskentakoostepalvelu.atarusuorituksetByOpiskelijaOid=/valintalaskentakoostepalvelu/resources/proxy/suoritukset/ataruSuorituksetByOpiskelijaOid/hakuOid/$1?fetchEnsikertalaisuus=false&shouldUseApplicationPersonOid=true valintalaskentakoostepalvelu.harkinnanvaraisuudet.atarutiedoille=/valintalaskentakoostepalvelu/resources/harkinnanvaraisuus/atarutiedoille +valintalaskentakoostepalvelu.harkinnanvaraisuudet.hakemuksille=/valintalaskentakoostepalvelu/resources/harkinnanvaraisuus/hakemuksille valintalaskenta-service.bypersonoid=/valintalaskenta-laskenta-service/resources/valintakoe/hakijat koski.oppija=/koski/api/oppija koski.sure=/koski/api/sure/oids diff --git a/suoritusrekisteri/src/test/scala/fi/vm/sade/hakurekisteri/ovara/OvaraServiceSpec.scala b/suoritusrekisteri/src/test/scala/fi/vm/sade/hakurekisteri/ovara/OvaraServiceSpec.scala index c3d831755f..0922b3cdc2 100644 --- a/suoritusrekisteri/src/test/scala/fi/vm/sade/hakurekisteri/ovara/OvaraServiceSpec.scala +++ b/suoritusrekisteri/src/test/scala/fi/vm/sade/hakurekisteri/ovara/OvaraServiceSpec.scala @@ -6,8 +6,15 @@ import akka.testkit.TestActorRef import fi.vm.sade.hakurekisteri.dates.{Ajanjakso, InFuture} import fi.vm.sade.hakurekisteri.{Config, MockConfig} import fi.vm.sade.hakurekisteri.ensikertalainen.EnsikertalainenActor -import fi.vm.sade.hakurekisteri.integration.hakemus.HakemusService +import fi.vm.sade.hakurekisteri.integration.hakemus.{ + AtaruHakemuksenHenkilotiedot, + AtaruHenkiloSearchParams, + HakemuksenHarkinnanvaraisuus, + HakemusService, + HakemusServiceMock +} import fi.vm.sade.hakurekisteri.integration.henkilo.OppijaNumeroRekisteri +import fi.vm.sade.hakurekisteri.integration.kooste.KoosteServiceMock import fi.vm.sade.hakurekisteri.integration.tarjonta.TarjontaActorRef import fi.vm.sade.hakurekisteri.integration.valintarekisteri.ValintarekisteriActorRef import fi.vm.sade.hakurekisteri.rest.support.HakurekisteriDriver @@ -18,7 +25,7 @@ import support.DbJournals import scala.concurrent.duration._ import scala.language.implicitConversions -import scala.concurrent.Await +import scala.concurrent.{Await, Future} class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { @@ -31,6 +38,53 @@ class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { var ovaraService: OvaraService = _ + val kkHakuOid = "1.2.3.4.44444" + val toinenAsteHakuOid = "1.2.3.4.55555" + + val hakemusServiceMocked = new HakemusServiceMock { + override def ataruhakemustenHenkilot( + params: AtaruHenkiloSearchParams + ): Future[List[AtaruHakemuksenHenkilotiedot]] = { + params.hakuOid.getOrElse("1.2.3.66666") match { + case oid if oid == kkHakuOid => + Future.successful( + Range(1, 201) + .map(i => AtaruHakemuksenHenkilotiedot("1.2.3.11." + i, Some("1.2.3.24." + i), None)) + .toList + ) + case oid if oid == toinenAsteHakuOid => + Future.successful( + Range(1, 201) + .map(i => AtaruHakemuksenHenkilotiedot("1.2.3.11." + i, Some("1.2.3.24." + i), None)) + .toList + ) + case _ => + Future.successful(List[AtaruHakemuksenHenkilotiedot]()) + } + } + } + + val koosteServiceMocked = new KoosteServiceMock { + override def getHarkinnanvaraisuudetForHakemusOidsWithTimeout( + hakemusOids: Seq[String], + timeout: Duration + ): Future[Seq[HakemuksenHarkinnanvaraisuus]] = { + Future.successful(hakemusOids.map(oid => HakemuksenHarkinnanvaraisuus(oid, None, List.empty))) + } + + override def getProxysuorituksetForHakemusOids( + hakuOid: String, + hakemusOids: Seq[String] + ): Future[Map[String, Map[String, String]]] = { + //koostepalvelun proxysuoritukset-rajapintaa kutsutaan hakemusOideilla, mutta se palauttaa mapin jossa + //avaimina ovat kyseisiltä hakemuksilta poimitut henkilöOidit. Simuloidaan sitä näin. + val result = hakemusOids + .map(hakemusOid => "1.2.3.24." + hakemusOid.split('.').last -> Map("key" -> "value")) + .toMap + Future.successful(result) + } + } + val hakuActorMock: TestActorRef[MockHakuActor] = TestActorRef[MockHakuActor]( Props( new MockHakuActor() @@ -51,9 +105,9 @@ class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { ) ) - val haku = Haku( + val someHakuToDoNothingWith = Haku( Kieliversiot(Some("haku"), None, None), - "1.1", + "1.1111", Ajanjakso(new DateTime(), InFuture), "kausi_s#1", 2014, @@ -67,10 +121,46 @@ class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { Some("hakutyyppi_01#1"), None ) + + val kkHaku = Haku( + Kieliversiot(Some("haku"), None, None), + kkHakuOid, + Ajanjakso(new DateTime(), InFuture), + "kausi_s#1", + 2014, + Some("kausi_k#1"), + Some(2015), + kkHaku = true, + toisenAsteenHaku = false, + None, + None, + "hakutapa_01#1", + Some("hakutyyppi_01#1"), + None + ) + + val toisenAsteenYhteishaku = Haku( + Kieliversiot(Some("haku"), None, None), + toinenAsteHakuOid, + Ajanjakso(new DateTime(), InFuture), + "kausi_s#1", + 2014, + Some("kausi_k#1"), + Some(2015), + kkHaku = false, + toisenAsteenHaku = true, + None, + None, + "hakutapa_01#1", + Some("hakutyyppi_01#1"), + None + ) + class MockHakuActor extends Actor { override def receive: Receive = { - case HakuRequest => sender ! AllHaut(Seq(haku)) - case msg: Int => sender ! msg.toString + case HakuRequest => + sender ! AllHaut(Seq(someHakuToDoNothingWith, kkHaku, toisenAsteenYhteishaku)) + case msg: Int => sender ! msg.toString } } @@ -82,7 +172,9 @@ class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { client, ekActorMock, hakuActorMock, - 1000 + 1000, + hakemusServiceMocked, + koosteServiceMocked ) super.beforeAll() } @@ -105,4 +197,39 @@ class OvaraServiceSpec extends FlatSpec with Matchers with BeforeAndAfterAll { println("result2" + result) result2.windowStart should equal(result.windowEnd) } + + it should "form harkinnanvaraisuudet" in { + val result = ovaraService.triggerDailyProcessing( + DailyProcessingParams( + "exec-id", + vainAktiiviset = true, + harkinnanvaraisuudet = true, + ensikertalaisuudet = false, + proxySuoritukset = false + ) + ) + //println("result" + result) + + result.size should equal(1) + result.head.total should equal(200) + result.head.tyyppi should equal("harkinnanvaraisuus") + } + + it should "form proxysuoritukset" in { + val result = ovaraService.triggerDailyProcessing( + DailyProcessingParams( + "exec-id", + vainAktiiviset = true, + harkinnanvaraisuudet = false, + ensikertalaisuudet = false, + proxySuoritukset = true + ) + ) + //println("result" + result) + + result.size should equal(1) + result.head.total should equal(200) + result.head.tyyppi should equal("proxysuoritukset") + } + }