Skip to content

Commit

Permalink
Rydd litt i periodisering
Browse files Browse the repository at this point in the history
  • Loading branch information
hestad committed Dec 4, 2024
1 parent 3ef18db commit f97ca5c
Show file tree
Hide file tree
Showing 9 changed files with 87 additions and 103 deletions.
1 change: 1 addition & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,10 @@ class Periode(
return rangeSet.asRanges().toPerioder()
}

fun tilstøter(other: Periode): Boolean = this.range.isConnected(other.range)
/**
* Sjekker om til og med i LHS er dagen før fra og med i RHS
*/
fun tilstøter(other: Periode): Boolean = this.tilOgMed.plusDays(1) == other.fraOgMed

override fun equals(other: Any?): Boolean {
if (this === other) return true
Expand Down Expand Up @@ -146,19 +149,19 @@ fun List<Periode>.inneholderOverlapp(): Boolean {
return false
}

fun List<Periode>.inneholderOverlappEllerTilstøter(): Boolean = this.inneholderOverlapp() || this.tilstøter()

/**
* @return true dersom listen har mindre enn to elementer, eller to eller flere perioder tilstøter
* Sjekker at for hver til og med er dagen før neste fra og med. Brukes gjerne i sammenhenger der man periodiserer en vedtaksperiode. Dette skal tilsvare logikken i [Periodisering]
*
* Vil returnere false dersom listen ikke er sortert, har hull eller overlapp.
* @return true dersom listen har mindre enn to elementer, eller alle periodene tilstøter hverandre.
*/
fun List<Periode>.tilstøter(): Boolean {
if (this.size < 2) {
return true
}
return this
.sortedWith(compareBy<Periode> { it.fraOgMed }.thenBy { it.tilOgMed })
.zipWithNext()
.any { (a, b) -> a.tilstøter(b) }
.all { (a, b) -> a.tilstøter(b) }
}

fun List<Periode>.leggSammen(godtaOverlapp: Boolean = true): List<Periode> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,34 @@ package no.nav.tiltakspenger.libs.periodisering

import java.time.LocalDate

/*
Denne klassen representerer en sammenhengende periode som kan ha ulike verdier for ulike deler av perioden.
Perioden kan ikke ha "hull" som ikke har en verdi
/**
* Denne klassen representerer en sammenhengende periode som kan ha ulike verdier for ulike deler av perioden.
* Perioden kan ikke ha "hull" som ikke har en verdi og periodene kan ikke overlappe.
* Periodene må være sortert.
*/
data class Periodisering<T>(
private val perioderMedVerdi: List<PeriodeMedVerdi<T>>,
) {
val perioderMedVerdi: List<PeriodeMedVerdi<T>>,
) : List<PeriodeMedVerdi<T>> by perioderMedVerdi {
constructor(vararg periodeMedVerdi: PeriodeMedVerdi<T>) : this(periodeMedVerdi.toList())

constructor(
initiellVerdi: T,
totalePeriode: Periode,
) : this(PeriodeMedVerdi(initiellVerdi, totalePeriode))

val perioder: List<Periode> by lazy { perioderMedVerdi.map { it.periode } }

init {
require(
perioderMedVerdi
.sortedBy { it.periode.fraOgMed }
.zipWithNext()
.all {
it.second.periode.fraOgMed ==
it.first.periode.tilOgMed
.plusDays(1)
},
) { "Ugyldig periodisering, for alle perioderMedVerdi gjelder at periode n+1 må starte dagen etter periode n slutter" }
zipWithNext()
.all { it.second.periode.fraOgMed == it.first.periode.tilOgMed.plusDays(1) },
) { "Ugyldig periodisering, for alle perioderMedVerdi gjelder at periode n+1 må starte dagen etter periode n slutter. Perioder: ${this.perioder}" }
}

val totalePeriode by lazy {
Periode(
perioderMedVerdi.minOf { it.periode.fraOgMed },
perioderMedVerdi.maxOf { it.periode.tilOgMed },
minOf { it.periode.fraOgMed },
maxOf { it.periode.tilOgMed },
)
}

Expand Down Expand Up @@ -70,16 +67,15 @@ data class Periodisering<T>(
throw IllegalArgumentException("Perioder som skal kombineres må være like")
}

return this.perioderMedVerdi
.flatMap { thisPeriodeMedVerdi ->
other.perioderMedVerdi.mapNotNull { otherPeriodeMedVerdi ->
thisPeriodeMedVerdi.periode.overlappendePeriode(otherPeriodeMedVerdi.periode)?.let {
PeriodeMedVerdi(sammensattVerdi(thisPeriodeMedVerdi.verdi, otherPeriodeMedVerdi.verdi), it)
}
return this.flatMap { thisPeriodeMedVerdi ->
other.mapNotNull { otherPeriodeMedVerdi ->
thisPeriodeMedVerdi.periode.overlappendePeriode(otherPeriodeMedVerdi.periode)?.let {
PeriodeMedVerdi(sammensattVerdi(thisPeriodeMedVerdi.verdi, otherPeriodeMedVerdi.verdi), it)
}
}.let {
Periodisering(it)
}
}.let {
Periodisering(it.sortedBy { it.periode.fraOgMed })
}
}

fun <U> map(kombinertVerdi: (T) -> U): Periodisering<U> =
Expand All @@ -88,8 +84,6 @@ data class Periodisering<T>(
.slåSammenTilstøtendePerioder()
.let { Periodisering(it) }

fun perioder(): List<PeriodeMedVerdi<T>> = perioderMedVerdi.sortedBy { it.periode.fraOgMed }

// Private hjelpemetoder:

private fun setPeriodeMedVerdi(delPeriodeMedVerdi: PeriodeMedVerdi<T>): Periodisering<T> {
Expand All @@ -105,7 +99,7 @@ data class Periodisering<T>(
perioderMedVerdi
.perioderMedUlikVerdi(delPeriodeMedVerdi.verdi)
.trekkFra(delPeriodeMedVerdi)
return Periodisering(nyePerioderMedSammeVerdi + nyePerioderMedUlikVerdi)
return Periodisering((nyePerioderMedSammeVerdi + nyePerioderMedUlikVerdi).sortedBy { it.periode.fraOgMed })
}

// Krever IKKE at alle elementer i lista har samme verdi for å fungere
Expand Down Expand Up @@ -134,6 +128,7 @@ data class Periodisering<T>(
.groupBy { it.verdi }
.values
.flatMap { listeMedLikeVerdier -> listeMedLikeVerdier.slåSammenTilstøtendePerioderMedSammeVerdi() }
.sortedBy { it.periode.fraOgMed }

// Krever at alle elementer i lista har samme verdi for å fungere!
private fun <T> List<PeriodeMedVerdi<T>>.leggTilPeriodeMedSammeVerdi(periodeMedVerdi: PeriodeMedVerdi<T>): List<PeriodeMedVerdi<T>> =
Expand Down Expand Up @@ -170,7 +165,7 @@ data class Periodisering<T>(
}
}
return this.copy(
perioderMedVerdi = beholdte.sortedBy { it.periode.fraOgMed },
perioderMedVerdi = beholdte,
)
}

Expand All @@ -181,17 +176,6 @@ data class Periodisering<T>(

override fun toString(): String =
"Periodisering(totalePeriode=$totalePeriode, perioderMedVerdi=${
perioderMedVerdi.sortedBy { it.periode.fraOgMed }.map { "\n" + it.toString() }
perioderMedVerdi.map { "\n" + it.toString() }
})"

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Periodisering<*>

return perioderMedVerdi.sortedBy { it.periode.fraOgMed } == other.perioderMedVerdi.sortedBy { it.periode.fraOgMed }
}

override fun hashCode(): Int = perioderMedVerdi.hashCode()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ class PeriodeMedVerdierAvLikTypeTest {
Periodisering(
Utfall.IKKE_OPPFYLT,
Periode(1.oktober(2023), 10.oktober(2023)),
)
.setVerdiForDelPeriode(Utfall.OPPFYLT, Periode(6.oktober(2023), 10.oktober(2023)))
).setVerdiForDelPeriode(Utfall.OPPFYLT, Periode(6.oktober(2023), 10.oktober(2023)))

val dagpenger =
Periodisering(
Expand All @@ -28,15 +27,13 @@ class PeriodeMedVerdierAvLikTypeTest {
)

val vedtak: Periodisering<Utfall> = aap.kombiner(dagpenger, ::kombinerToUfall)
vedtak.perioder().size shouldBe 2
vedtak.size shouldBe 2

vedtak.perioder().count { it.verdi == Utfall.OPPFYLT } shouldBe 1
vedtak.perioder()
.find { it.verdi == Utfall.OPPFYLT }!!.periode shouldBe Periode(6.oktober(2023), 10.oktober(2023))
vedtak.count { it.verdi == Utfall.OPPFYLT } shouldBe 1
vedtak.find { it.verdi == Utfall.OPPFYLT }!!.periode shouldBe Periode(6.oktober(2023), 10.oktober(2023))

vedtak.perioder().count { it.verdi == Utfall.IKKE_OPPFYLT } shouldBe 1
vedtak.perioder()
.find { it.verdi == Utfall.IKKE_OPPFYLT }!!.periode shouldBe Periode(1.oktober(2023), 5.oktober(2023))
vedtak.count { it.verdi == Utfall.IKKE_OPPFYLT } shouldBe 1
vedtak.find { it.verdi == Utfall.IKKE_OPPFYLT }!!.periode shouldBe Periode(1.oktober(2023), 5.oktober(2023))
}

@Test
Expand Down Expand Up @@ -72,18 +69,14 @@ class PeriodeMedVerdierAvLikTypeTest {

val vedtak = alleVilkår.reduser(::kombinerToUfall)
println(vedtak)
vedtak.perioder().size shouldBe 3
vedtak.size shouldBe 3

vedtak.perioder()
.count { it.verdi == Utfall.OPPFYLT } shouldBe 1
vedtak.perioder()
.filter { it.verdi == Utfall.OPPFYLT }
vedtak.count { it.verdi == Utfall.OPPFYLT } shouldBe 1
vedtak.filter { it.verdi == Utfall.OPPFYLT }
.map { it.periode } shouldContainExactly listOf(Periode(3.oktober(2023), 4.oktober(2023)))

vedtak.perioder()
.count { it.verdi == Utfall.IKKE_OPPFYLT } shouldBe 2
vedtak.perioder()
.filter { it.verdi == Utfall.IKKE_OPPFYLT }.map { it.periode } shouldContainExactly listOf(
vedtak.count { it.verdi == Utfall.IKKE_OPPFYLT } shouldBe 2
vedtak.filter { it.verdi == Utfall.IKKE_OPPFYLT }.map { it.periode } shouldContainExactly listOf(
Periode(1.oktober(2023), 2.oktober(2023)),
Periode(5.oktober(2023), 10.oktober(2023)),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class PeriodeMedVerdierAvUlikTypeTest {
.setVerdiForDelPeriode(300L, Periode(8.oktober(2023), 8.oktober(2023)))
.setVerdiForDelPeriode(300L, Periode(9.oktober(2023), 9.oktober(2023)))
.setVerdiForDelPeriode(300L, Periode(10.oktober(2023), 10.oktober(2023)))
perioderMedDagsats.perioder().size shouldBe 1
perioderMedDagsats.size shouldBe 1
}

@Test
Expand All @@ -48,7 +48,7 @@ class PeriodeMedVerdierAvUlikTypeTest {
.setVerdiForDelPeriode(301L, Periode(8.oktober(2023), 8.oktober(2023)))
.setVerdiForDelPeriode(300L, Periode(9.oktober(2023), 9.oktober(2023)))
.setVerdiForDelPeriode(301L, Periode(10.oktober(2023), 10.oktober(2023)))
perioderMedDagsats.perioder().size shouldBe 10
perioderMedDagsats.size shouldBe 10
}

@Test
Expand All @@ -65,26 +65,26 @@ class PeriodeMedVerdierAvUlikTypeTest {
.setVerdiForDelPeriode(301L, Periode(8.oktober(2023), 8.oktober(2023)))
.setVerdiForDelPeriode(300L, Periode(9.oktober(2023), 9.oktober(2023)))
.setVerdiForDelPeriode(300L, Periode(10.oktober(2023), 10.oktober(2023)))
perioderMedDagsats.perioder().size shouldBe 5
perioderMedDagsats.perioder().count { it.verdi == 300L } shouldBe 3
perioderMedDagsats.perioder().count { it.verdi == 301L } shouldBe 2
perioderMedDagsats.perioder().count { it.verdi == 255L } shouldBe 0
perioderMedDagsats.size shouldBe 5
perioderMedDagsats.count { it.verdi == 300L } shouldBe 3
perioderMedDagsats.count { it.verdi == 301L } shouldBe 2
perioderMedDagsats.count { it.verdi == 255L } shouldBe 0
}

@Test
fun `en periode som er en kombinasjon av to verdier kan splittes opp igjen i de enkeltstående verdiene`() {
fun kontrollerDagsats(perioderMedDagsats: Periodisering<Long>) {
perioderMedDagsats.perioder().size shouldBe 2
perioderMedDagsats.perioder().count { it.verdi == 300L } shouldBe 1
perioderMedDagsats.perioder().count { it.verdi == 301L } shouldBe 1
perioderMedDagsats.perioder().count { it.verdi == 255L } shouldBe 0
perioderMedDagsats.size shouldBe 2
perioderMedDagsats.count { it.verdi == 300L } shouldBe 1
perioderMedDagsats.count { it.verdi == 301L } shouldBe 1
perioderMedDagsats.count { it.verdi == 255L } shouldBe 0
}

fun kontrollerAntallBarn(perioderMedAntallBarn: Periodisering<Int>) {
perioderMedAntallBarn.perioder().size shouldBe 3
perioderMedAntallBarn.perioder().count { it.verdi == 1 } shouldBe 2
perioderMedAntallBarn.perioder().count { it.verdi == 2 } shouldBe 1
perioderMedAntallBarn.perioder().count { it.verdi == 0 } shouldBe 0
perioderMedAntallBarn.size shouldBe 3
perioderMedAntallBarn.count { it.verdi == 1 } shouldBe 2
perioderMedAntallBarn.count { it.verdi == 2 } shouldBe 1
perioderMedAntallBarn.count { it.verdi == 0 } shouldBe 0
}

val perioderMedDagsats =
Expand All @@ -105,7 +105,7 @@ class PeriodeMedVerdierAvUlikTypeTest {

val perioderMedDagsatsOgAntallBarn =
perioderMedDagsats.kombiner(perioderMedAntallBarn, DagsatsOgAntallBarn::kombinerDagsatsOgAntallBarn)
perioderMedDagsatsOgAntallBarn.perioder().size shouldBe 4
perioderMedDagsatsOgAntallBarn.size shouldBe 4

kontrollerDagsats(perioderMedDagsatsOgAntallBarn.map(DagsatsOgAntallBarn::trekkUtDagsats))
kontrollerAntallBarn(perioderMedDagsatsOgAntallBarn.map(DagsatsOgAntallBarn::trekkUtAntallBarn))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class PeriodeMedVerdierKombinasjonTest {
val totaleVedtaksPerioder: Periodisering<UtfallOgDagsatsOgAntallBarn> =
perioderMedDagsatsOgAntallBarn.kombiner(innvilgetPerioder, UtfallOgDagsatsOgAntallBarn::kombiner)

totaleVedtaksPerioder.perioder().size shouldBe 4
totaleVedtaksPerioder.size shouldBe 4
}

private fun kombinerToUfall(en: Utfall, to: Utfall): Utfall {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PeriodiseringMedNullableTest {
.setVerdiForDelPeriode(null, periode2)
.setVerdiForDelPeriode(null, periode3)

periodisering.perioder().size shouldBe 2
periodisering.size shouldBe 2
}

@Test
Expand All @@ -37,7 +37,7 @@ class PeriodiseringMedNullableTest {
}
}

total.perioder().size shouldBe 1
total.perioder().first().verdi shouldBe 6
total.size shouldBe 1
total.first().verdi shouldBe 6
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package no.nav.tiltakspenger.libs.periodisering

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import java.time.LocalDate

Expand Down Expand Up @@ -31,25 +32,27 @@ class PeriodiseringTest {
PeriodeMedVerdi("bar", Periode(LocalDate.of(2023, 1, 22), LocalDate.of(2023, 1, 24))),
),
)
}
}.message shouldBe "Ugyldig periodisering, for alle perioderMedVerdi gjelder at periode n+1 må starte dagen etter periode n slutter. Perioder: [Periode(fraOgMed=2023-01-01 tilOgMed=2023-01-20), Periode(fraOgMed=2023-01-22 tilOgMed=2023-01-24)]"
}

@Test
fun `skal kunne opprette en periodisering med perioder i ikke-kronologisk rekkefølge`() {
Periodisering(
listOf(
PeriodeMedVerdi("bar", Periode(LocalDate.of(2023, 1, 22), LocalDate.of(2023, 1, 24))),
PeriodeMedVerdi("foo", Periode(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 21))),
),
)
fun `skal ikke kunne opprette en periodisering med perioder i ikke-kronologisk rekkefølge`() {
shouldThrow<IllegalArgumentException> {
Periodisering(
listOf(
PeriodeMedVerdi("bar", Periode(LocalDate.of(2023, 1, 22), LocalDate.of(2023, 1, 24))),
PeriodeMedVerdi("foo", Periode(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 21))),
),
)
}.message shouldBe "Ugyldig periodisering, for alle perioderMedVerdi gjelder at periode n+1 må starte dagen etter periode n slutter. Perioder: [Periode(fraOgMed=2023-01-22 tilOgMed=2023-01-24), Periode(fraOgMed=2023-01-01 tilOgMed=2023-01-21)]"
}

@Test
fun `to periodiseringer med like perioder i ulik rekkefølge skal være like`() {
fun `to periodiseringer med like perioderskal være like`() {
val periodisering1 = Periodisering(
listOf(
PeriodeMedVerdi("bar", Periode(LocalDate.of(2023, 1, 22), LocalDate.of(2023, 1, 24))),
PeriodeMedVerdi("foo", Periode(LocalDate.of(2023, 1, 1), LocalDate.of(2023, 1, 21))),
PeriodeMedVerdi("bar", Periode(LocalDate.of(2023, 1, 22), LocalDate.of(2023, 1, 24))),
),
)
val periodisering2 = Periodisering(
Expand Down
Loading

0 comments on commit f97ca5c

Please sign in to comment.