Skip to content

Commit

Permalink
Merge pull request #3258 from Opetushallitus/TOR-2271-sure-by-oppija
Browse files Browse the repository at this point in the history
TOR-2271 Tulosten ryhmittely 100 oppijaa per tiedosto + master-oid mäppäys
  • Loading branch information
a544jh authored Dec 30, 2024
2 parents d45a9ac + 7c51752 commit 435239d
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@ case class SuoritusrekisteriMuuttuneetJalkeenQuery(
QueryMethods.runDbSync(
db,
sql"""
SELECT opiskeluoikeus.id, opiskeluoikeus.aikaleima, coalesce(henkilo.master_oid, henkilo.oid)
SELECT opiskeluoikeus.id,
opiskeluoikeus.aikaleima,
COALESCE(henkilo.master_oid, henkilo.oid) AS master_oid
FROM opiskeluoikeus
JOIN henkilo ON henkilo.oid = opiskeluoikeus.oppija_oid
WHERE aikaleima >= ${Timestamp.valueOf(muuttuneetJälkeen)}
AND koulutusmuoto = any(${SuoritusrekisteriQuery.opiskeluoikeudenTyypit})
ORDER BY aikaleima
JOIN henkilo ON henkilo.oid = opiskeluoikeus.oppija_oid
WHERE COALESCE(henkilo.master_oid, henkilo.oid) IN (
SELECT DISTINCT COALESCE(h.master_oid, h.oid) AS master_oid
FROM henkilo h
JOIN opiskeluoikeus o ON h.oid = o.oppija_oid
WHERE aikaleima >= ${Timestamp.valueOf(muuttuneetJälkeen)}
AND o.koulutusmuoto = ANY(${SuoritusrekisteriQuery.opiskeluoikeudenTyypit}))
ORDER BY opiskeluoikeus.aikaleima
""".as[(Int, Timestamp, String)])
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ case class SuoritusrekisteriOppijaOidsQuery(
QueryMethods.runDbSync(
db,
sql"""
SELECT opiskeluoikeus.id, opiskeluoikeus.aikaleima, coalesce(henkilo.master_oid, henkilo.oid) as oid
SELECT opiskeluoikeus.id, opiskeluoikeus.aikaleima, coalesce(henkilo.master_oid, henkilo.oid) as master_oid
FROM opiskeluoikeus
JOIN henkilo ON henkilo.oid = opiskeluoikeus.oppija_oid
WHERE
(henkilo.oid = any($oppijaOids) OR
henkilo.master_oid = any($oppijaOids))
AND koulutusmuoto = any(${SuoritusrekisteriQuery.opiskeluoikeudenTyypit})
JOIN henkilo ON henkilo.oid = opiskeluoikeus.oppija_oid
WHERE COALESCE(henkilo.master_oid, henkilo.oid) IN (
SELECT DISTINCT COALESCE(h.master_oid, h.oid)
FROM henkilo h
WHERE h.oid = any($oppijaOids)
)
AND opiskeluoikeus.koulutusmuoto = any(${SuoritusrekisteriQuery.opiskeluoikeudenTyypit})
""".as[(Int, Timestamp, String)])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import fi.oph.koski.db.{DB, KoskiOpiskeluoikeusRow, KoskiTables, QueryMethods}
import fi.oph.koski.koskiuser.KoskiSpecificSession
import fi.oph.koski.koskiuser.Rooli.{OPHKATSELIJA, OPHPAAKAYTTAJA}
import fi.oph.koski.log._
import fi.oph.koski.massaluovutus.suoritusrekisteri.opiskeluoikeus.SureOpiskeluoikeus
import fi.oph.koski.massaluovutus.{MassaluovutusQueryParameters, MassaluovutusQueryPriority, QueryResultWriter}
import fi.oph.koski.schema.{KoskeenTallennettavaOpiskeluoikeus, KoskiSchema}

Expand All @@ -21,34 +22,41 @@ trait SuoritusrekisteriQuery extends MassaluovutusQueryParameters with Logging {

override def run(application: KoskiApplication, writer: QueryResultWriter)(implicit user: KoskiSpecificSession): Either[String, Unit] = {
val opiskeluoikeudetResult = getOpiskeluoikeusIds(application.masterDatabase.db)
val oppijaOidit = opiskeluoikeudetResult.groupBy(_._3)
val resultsByOppija = opiskeluoikeudetResult.groupBy(_._3)

writer.predictFileCount(oppijaOidit.size)
oppijaOidit.grouped(100).foreach { groupedResult =>
val db = selectDbByLag(application, groupedResult.head._2.head._2)
groupedResult.foreach { case (oppija_oid, opiskeluoikeudet) =>
writer.predictFileCount(resultsByOppija.size / 100)
resultsByOppija.grouped(100).zipWithIndex.foreach { case (oppijaResult, index) =>
val sureResponses = oppijaResult.map { case (oppija_oid, opiskeluoikeudet) =>
val latestTimestamp = opiskeluoikeudet.maxBy(_._2.toInstant)._2
val db = selectDbByLag(application, latestTimestamp)
val response = opiskeluoikeudet.flatMap(oo => getOpiskeluoikeus(application, db, oo._1))
response.foreach { oo =>
auditLog(oo.oppijaOid, oo.opiskeluoikeus.oid)
auditLog(oppija_oid, oo.oid)
}
writer.putJson(s"$oppija_oid", response)
SureResponse(
oppijaOid = oppija_oid,
kaikkiOidit = application.henkilöRepository.findByOid(oppija_oid).get.kaikkiOidit,
aikaleima = LocalDateTime.from(latestTimestamp.toLocalDateTime),
opiskeluoikeudet = response
)
}
writer.putJson(s"$index", sureResponses)
}
Right(())
}

override def queryAllowed(application: KoskiApplication)(implicit user: KoskiSpecificSession): Boolean =
user.hasRole(OPHKATSELIJA) || user.hasRole(OPHPAAKAYTTAJA)

private def getOpiskeluoikeus(application: KoskiApplication, db: DB, id: Int): Option[SureResponse] =
private def getOpiskeluoikeus(application: KoskiApplication, db: DB, id: Int): Option[SureOpiskeluoikeus] =
QueryMethods.runDbSync(
db,
sql"""
SELECT *
FROM opiskeluoikeus
WHERE id = $id
""".as[KoskiOpiskeluoikeusRow]
).headOption.flatMap(toResponse(application))
).headOption.flatMap(toSureOpiskeluoikeus(application))

private def selectDbByLag(application: KoskiApplication, opiskeluoikeusAikaleima: Timestamp): DB = {
val safetyLimit = 15.seconds
Expand All @@ -62,11 +70,11 @@ trait SuoritusrekisteriQuery extends MassaluovutusQueryParameters with Logging {
}
}

private def toResponse(application: KoskiApplication)(row: KoskiOpiskeluoikeusRow): Option[SureResponse] = {
private def toSureOpiskeluoikeus(application: KoskiApplication)(row: KoskiOpiskeluoikeusRow): Option[SureOpiskeluoikeus] = {
val json = KoskiTables.KoskiOpiskeluoikeusTable.readAsJValue(row.data, row.oid, row.versionumero, row.aikaleima)
application.validatingAndResolvingExtractor.extract[KoskeenTallennettavaOpiskeluoikeus](KoskiSchema.strictDeserialization)(json) match {
case Right(oo: KoskeenTallennettavaOpiskeluoikeus) =>
SureOpiskeluoikeus(oo).map(SureResponse(row.oppijaOid, row.aikaleima.toLocalDateTime, _))
SureOpiskeluoikeusO(oo)
case Left(errors) =>
logger.warn(s"Error deserializing opiskeluoikeus: ${errors}")
None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import java.time.LocalDateTime

case class SureResponse(
oppijaOid: String,
kaikkiOidit: Seq[String],
aikaleima: LocalDateTime,
opiskeluoikeus: SureOpiskeluoikeus,
opiskeluoikeudet: Seq[SureOpiskeluoikeus],
)

object SureResponse {
lazy val schemaJson: JValue =
SchemaToJson.toJsonSchema(KoskiSchema.createSchema(classOf[SureResponse]).asInstanceOf[ClassSchema])
}

object SureOpiskeluoikeus {
object SureOpiskeluoikeusO {
def apply(oo: KoskeenTallennettavaOpiskeluoikeus): Option[SureOpiskeluoikeus] =
(oo match {
case o: PerusopetuksenOpiskeluoikeus => Some(SurePerusopetuksenOpiskeluoikeus(o))
Expand Down
106 changes: 93 additions & 13 deletions src/test/scala/fi/oph/koski/massaluovutus/MassaluovutusSpec.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package fi.oph.koski.massaluovutus

import fi.oph.koski.api.misc.OpiskeluoikeusTestMethodsAmmatillinen
import fi.oph.koski.db.PostgresDriverWithJsonSupport.api.actionBasedSQLInterpolation
import fi.oph.koski.db.QueryMethods
import fi.oph.koski.henkilo.KoskiSpecificMockOppijat
Expand All @@ -14,6 +15,7 @@ import fi.oph.koski.massaluovutus.valintalaskenta.ValintalaskentaQuery
import fi.oph.koski.organisaatio.MockOrganisaatiot
import fi.oph.koski.raportit.RaportitService
import fi.oph.koski.schema.KoskiSchema.strictDeserialization
import fi.oph.koski.schema.{KoskeenTallennettavaOpiskeluoikeus, LocalizedString, PerusopetuksenVuosiluokanSuoritus}
import fi.oph.koski.util.Wait
import fi.oph.koski.{KoskiApplicationForTests, KoskiHttpSpec}
import fi.oph.scalaschema.Serializer.format
Expand All @@ -27,9 +29,8 @@ import java.net.URL
import java.sql.Timestamp
import java.time.{Duration, LocalDate, LocalDateTime}
import java.util.UUID
import scala.util.matching.Regex

class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach {
class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers with BeforeAndAfterAll with BeforeAndAfterEach with OpiskeluoikeusTestMethodsAmmatillinen {
val app = KoskiApplicationForTests

override protected def beforeAll(): Unit = {
Expand Down Expand Up @@ -506,9 +507,8 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
oos.arr.map(v =>
(
(v \ "oppijaOid").extract[String],
(v \ "opiskeluoikeus" \ "oid").extract[String],
)).foreach { case (oppijaOid, opiskeluoikeusOid) =>
AuditLogTester.verifyLastAuditLogMessage(Map(
((v \ "opiskeluoikeudet").extract[List[JObject]].last \ "oid").extract[String])).last match {
case (oppijaOid, opiskeluoikeusOid) => AuditLogTester.verifyLastAuditLogMessage(Map(
"operation" -> "SUORITUSREKISTERI_OPISKELUOIKEUS_HAKU",
"target" -> Map(
"oppijaHenkiloOid" -> oppijaOid,
Expand All @@ -517,8 +517,6 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
))
}
}


}
}

Expand All @@ -540,7 +538,10 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
val oos = jsonFiles.flatMap {
case JArray(a) => a
case _ => Nil
}.map(_ \ "opiskeluoikeus")
}.map(_ \ "opiskeluoikeudet").flatMap {
case JArray(a) => a
case _ => Nil
}
tyyppi match {
case Some(t) => oos.filter(oo => (oo \ "tyyppi" \ "koodiarvo").extract[String] == t)
case None => oos
Expand Down Expand Up @@ -634,6 +635,33 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
))
}
}

"Palauttaa oppijan kaikki opiskeluoikeudet jos yksikin on muuttunut" in {
val oppija = KoskiSpecificMockOppijat.moniaEriOpiskeluoikeuksia
val oo = getOpiskeluoikeus(oppija.oid, "perusopetus")
val muokattuOo = oo.withSuoritukset(oo.suoritukset.map {
case p: PerusopetuksenVuosiluokanSuoritus => p.copy(todistuksellaNäkyvätLisätiedot = Some(LocalizedString.finnish("asd")))
case s => s
})
createOrUpdate(oppija, muokattuOo)
val tallennettuOo = getOpiskeluoikeus(muokattuOo.oid.get).asInstanceOf[KoskeenTallennettavaOpiskeluoikeus]

val query = getQuery(tallennettuOo.aikaleima.get)
val queryId = addQuerySuccessfully(query, user) { response =>
response.status should equal(QueryState.pending)
response.queryId
}
val complete = waitForCompletion(queryId, user)

val jsonFiles = complete.files.map { file =>
verifyResultAndContent(file, user) {
JsonMethods.parse(response.body)
}
}
val oppijat = jsonFiles.head.extract[Seq[JObject]]
oppijat.length should equal(1)
(oppijat.head \ "opiskeluoikeudet").extract[Seq[JObject]].length should equal(7)
}
}

"Suoritusrekisterikysely - oppija-oideilla hakeminen" - {
Expand Down Expand Up @@ -679,9 +707,8 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
oos.arr.map(v =>
(
(v \ "oppijaOid").extract[String],
(v \ "opiskeluoikeus" \ "oid").extract[String],
)).foreach { case (oppijaOid, opiskeluoikeusOid) =>
AuditLogTester.verifyLastAuditLogMessage(Map(
((v \ "opiskeluoikeudet").extract[List[JObject]].last \ "oid").extract[String])).last match {
case (oppijaOid, opiskeluoikeusOid) => AuditLogTester.verifyLastAuditLogMessage(Map(
"operation" -> "SUORITUSREKISTERI_OPISKELUOIKEUS_HAKU",
"target" -> Map(
"oppijaHenkiloOid" -> oppijaOid,
Expand All @@ -690,7 +717,6 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
))
}
}

}
}

Expand All @@ -712,7 +738,10 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
val oos = jsonFiles.flatMap {
case JArray(a) => a
case _ => Nil
}.map(_ \ "opiskeluoikeus")
}.map(_ \ "opiskeluoikeudet").flatMap {
case JArray(a) => a
case _ => Nil
}
tyyppi match {
case Some(t) => oos.filter(oo => (oo \ "tyyppi" \ "koodiarvo").extract[String] == t)
case None => oos
Expand Down Expand Up @@ -808,6 +837,57 @@ class MassaluovutusSpec extends AnyFreeSpec with KoskiHttpSpec with Matchers wit
))
}
}

"Haku slave oidilla palauttaa master-oppijan" in {
val query = getQuery(Seq(KoskiSpecificMockOppijat.slave.henkilö.oid))
val queryId = addQuerySuccessfully(query, user) { response =>
response.status should equal(QueryState.pending)
response.queryId
}
val complete = waitForCompletion(queryId, user)

val jsonFiles = complete.files.map { file =>
verifyResultAndContent(file, user) {
JsonMethods.parse(response.body)
}
}

(jsonFiles.head.extract[Seq[JObject]].head \ "oppijaOid").extract[String] should equal(KoskiSpecificMockOppijat.master.oid)
}

"Haku master oidilla palauttaa oppijan slave-oidineen" in {
val query = getQuery(Seq(KoskiSpecificMockOppijat.master.oid))
val queryId = addQuerySuccessfully(query, user) { response =>
response.status should equal(QueryState.pending)
response.queryId
}
val complete = waitForCompletion(queryId, user)

val jsonFiles = complete.files.map { file =>
verifyResultAndContent(file, user) {
JsonMethods.parse(response.body)
}
}

(jsonFiles.head.extract[Seq[JObject]].head \ "kaikkiOidit").extract[Set[String]] should equal(Set(KoskiSpecificMockOppijat.master.oid, KoskiSpecificMockOppijat.slave.henkilö.oid))
}

"Haku master- ja slave oidilla samaan aikaan palauttaa vain yhden oppijan" in {
val query = getQuery(Seq(KoskiSpecificMockOppijat.master.oid, KoskiSpecificMockOppijat.slave.henkilö.oid))
val queryId = addQuerySuccessfully(query, user) { response =>
response.status should equal(QueryState.pending)
response.queryId
}
val complete = waitForCompletion(queryId, user)

val jsonFiles = complete.files.map { file =>
verifyResultAndContent(file, user) {
JsonMethods.parse(response.body)
}
}

jsonFiles.head.extract[Seq[JObject]].length should equal(1)
}
}

def addQuery[T](query: MassaluovutusQueryParameters, user: UserWithPassword)(f: => T): T =
Expand Down

0 comments on commit 435239d

Please sign in to comment.