Skip to content

Commit

Permalink
Perusopetuksen päällekkäisten opiskeluoikeuksien validointi
Browse files Browse the repository at this point in the history
  • Loading branch information
ilkkahanninen committed Dec 29, 2023
1 parent 06571e4 commit 98d589b
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import fi.oph.koski.koskiuser.KoskiSpecificSession
import fi.oph.koski.organisaatio.OrganisaatioRepository
import fi.oph.koski.perustiedot.{OpiskeluoikeudenPerustiedot, PerustiedotSyncRepository}
import fi.oph.koski.schema._
import fi.oph.koski.util.DateRange
import fi.oph.scalaschema.Serializer.format
import org.json4s._
import slick.dbio.Effect.{Read, Transactional, Write}
Expand Down Expand Up @@ -116,6 +117,8 @@ class PostgresKoskiOpiskeluoikeusRepositoryActions(
lazy val aiempiOpiskeluoikeusPäättynyt = rows.exists(_.toOpiskeluoikeusUnsafe.tila.opiskeluoikeusjaksot.last.opiskeluoikeusPäättynyt)

opiskeluoikeus match {
case oo: PerusopetuksenOpiskeluoikeus =>
!päällekkäinenOpiskeluoikeusExists(oo, rows)
case _: MuunKuinSäännellynKoulutuksenOpiskeluoikeus =>
true
case _: TaiteenPerusopetuksenOpiskeluoikeus =>
Expand All @@ -129,6 +132,14 @@ class PostgresKoskiOpiskeluoikeusRepositoryActions(
}
}

private def päällekkäinenOpiskeluoikeusExists(opiskeluoikeus: PerusopetuksenOpiskeluoikeus, rows: List[KoskiOpiskeluoikeusRow]): Boolean = {
val range = DateRange(opiskeluoikeus.alkamispäivä, opiskeluoikeus.päättymispäivä)
rows.exists { row =>
val range2 = DateRange(Some(row.alkamispäivä.toLocalDate), row.päättymispäivä.map(_.toLocalDate))
range.intersects(range2)
}
}

private def isMuuAmmatillinenOpiskeluoikeus(opiskeluoikeus: AmmatillinenOpiskeluoikeus): Boolean =
opiskeluoikeus.suoritukset.forall {
case _: MuunAmmatillisenKoulutuksenSuoritus => true
Expand Down
18 changes: 18 additions & 0 deletions src/main/scala/fi/oph/koski/util/DateRange.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package fi.oph.koski.util

import java.time.LocalDate
import fi.oph.koski.util.ChainingSyntax._

case class DateRange(
start: Option[LocalDate],
end: Option[LocalDate],
) {
def contains(date: LocalDate): Boolean =
date.isEqualOrAfter(start.getOrElse(LocalDate.MIN)) && date.isEqualOrBefore(end.getOrElse(LocalDate.MAX))

def intersects(other: DateRange): Boolean =
other.start.exists(contains) ||
other.end.exists(contains) ||
start.exists(other.contains) ||
end.exists(other.contains)
}
80 changes: 80 additions & 0 deletions src/test/scala/fi/oph/koski/api/misc/DateRangeSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package fi.oph.koski.api.misc

import fi.oph.koski.util.DateRange
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers._

import java.time.LocalDate

class DateRangeSpec extends AnyFreeSpec {
"DateRange" - {
def closedRange(a: LocalDate, b: LocalDate) = DateRange(Some(a), Some(b))
def openEndedRange(a: LocalDate) = DateRange(Some(a), None)

"Suljettu aikaväli vs suljettu aikaväli" - {
"a ennen b -> eivät leikkaa" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = closedRange(LocalDate.of(2001, 1, 1), LocalDate.of(2001, 12, 30))
a.intersects(b) should equal (false)
}

"a jälkeen b -> eivät leikkaa" in {
val a = closedRange(LocalDate.of(2001, 1, 1), LocalDate.of(2001, 12, 30))
val b = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
a.intersects(b) should equal(false)
}

"a ympäröi b -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = closedRange(LocalDate.of(2000, 2, 1), LocalDate.of(2000, 3, 30))
a.intersects(b) should equal(true)
}

"a ja b lomittain, a alkaa ennen b -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = closedRange(LocalDate.of(2000, 2, 1), LocalDate.of(2001, 3, 30))
a.intersects(b) should equal(true)
}

"a ja b lomittain, b alkaa ennen a -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 2, 1), LocalDate.of(2001, 3, 30))
val b = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
a.intersects(b) should equal(true)
}
}

"Suljettu aikaväli vs puoliavoin aikaväli" - {
"a ennen b -> eivät leikkaa" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = openEndedRange(LocalDate.of(2001, 1, 1))
a.intersects(b) should equal(false)
}

"a jälkeen b -> leikkaavat" in {
val a = closedRange(LocalDate.of(2001, 1, 1), LocalDate.of(2001, 12, 30))
val b = openEndedRange(LocalDate.of(2000, 1, 1))
a.intersects(b) should equal(true)
}

"a ympäröi b -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = openEndedRange(LocalDate.of(2000, 2, 1))
a.intersects(b) should equal(true)
}

"a ja b lomittain, a alkaa ennen b -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 1, 1), LocalDate.of(2000, 12, 30))
val b = openEndedRange(LocalDate.of(2000, 2, 1))
a.intersects(b) should equal(true)
}

"a ja b lomittain, b alkaa ennen a -> leikkaavat" in {
val a = closedRange(LocalDate.of(2000, 2, 1), LocalDate.of(2001, 3, 30))
val b = openEndedRange(LocalDate.of(2000, 1, 1))
a.intersects(b) should equal(true)
}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,31 @@ class OppijaValidationPerusopetusSpec extends TutkinnonPerusteetTest[Perusopetuk
}
}

"Samaa opiskeluoikeutta ei voi siirää kahteen kertaan, vaikka päivämäärät ovat erilaiset (mutta päällekkäiset) ja se olisikin terminaalitilassa, variantti" in {
val opiskeluoikeus = defaultOpiskeluoikeus.copy(
tila = NuortenPerusopetuksenOpiskeluoikeudenTila(List(
NuortenPerusopetuksenOpiskeluoikeusjakso(LocalDate.of(2015, 8, 15), opiskeluoikeusLäsnä),
NuortenPerusopetuksenOpiskeluoikeusjakso(LocalDate.of(2016, 6, 4), opiskeluoikeusValmistunut),
))
)

val opiskeluoikeus2 = opiskeluoikeus.copy(
tila = NuortenPerusopetuksenOpiskeluoikeudenTila(List(
NuortenPerusopetuksenOpiskeluoikeusjakso(LocalDate.of(2015, 8, 15), opiskeluoikeusLäsnä),
)),
suoritukset = List(
vuosiluokkasuoritus.copy(alkamispäivä = Some(LocalDate.of(2015, 8, 15))),
)
)

setupOppijaWithOpiskeluoikeus(opiskeluoikeus, defaultHenkilö) {
verifyResponseStatusOk()
}
postOppija(makeOppija(defaultHenkilö, List(opiskeluoikeus2))) {
verifyResponseStatus(409, KoskiErrorCategory.conflict.exists())
}
}

}

private def setupOppijaWithAndGetOpiskeluoikeus(oo: KoskeenTallennettavaOpiskeluoikeus): PerusopetuksenOpiskeluoikeus = setupOppijaWithOpiskeluoikeus(oo) {
Expand Down

0 comments on commit 98d589b

Please sign in to comment.