Skip to content

Beregning av begrenset revurdering #668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
<properties>
<java.version>21</java.version>
<kotlin.version>2.1.0</kotlin.version>
<bidrag-felles.version>2025.02.03.142844</bidrag-felles.version>
<bidrag-beregn-felles.version>2025.02.03.104338</bidrag-beregn-felles.version>
<bidrag-felles.version>2025.02.06.105637</bidrag-felles.version>
<bidrag-beregn-felles.version>2025.02.06.162730</bidrag-beregn-felles.version>
<h2.version>2.3.232</h2.version>
<logback-encoder.version>8.0</logback-encoder.version>
<token-support.version>3.2.0</token-support.version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package no.nav.bidrag.behandling.consumer

import no.nav.bidrag.commons.web.client.AbstractRestClient
import no.nav.bidrag.domene.ident.Personident
import no.nav.bidrag.transport.behandling.stonad.request.HentStønadHistoriskRequest
import no.nav.bidrag.transport.behandling.stonad.request.LøpendeBidragssakerRequest
import no.nav.bidrag.transport.behandling.stonad.request.SkyldnerStønaderRequest
import no.nav.bidrag.transport.behandling.stonad.response.LøpendeBidragssakerResponse
import no.nav.bidrag.transport.behandling.stonad.response.SkyldnerStønaderResponse
import no.nav.bidrag.transport.behandling.stonad.response.StønadDto
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.retry.annotation.Backoff
Expand Down Expand Up @@ -45,4 +47,15 @@ class BidragStønadConsumer(
bidragsStønadUri.pathSegment("hent-alle-stonader-for-skyldner").build().toUri(),
SkyldnerStønaderRequest(personidentBidragspliktig),
)

@Retryable(
value = [Exception::class],
maxAttempts = 3,
backoff = Backoff(delay = 200, maxDelay = 1000, multiplier = 2.0),
)
fun hentHistoriskeStønader(request: HentStønadHistoriskRequest): StønadDto? =
postForEntity(
bidragsStønadUri.pathSegment("hent-stonad-historisk/").build().toUri(),
request,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package no.nav.bidrag.behandling.controller.v2

import io.github.oshai.kotlinlogging.KotlinLogging
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.security.SecurityRequirement
import jakarta.validation.Valid
import no.nav.bidrag.behandling.dto.v1.behandling.OppdaterOpphørsdatoRequestDto
import no.nav.bidrag.behandling.dto.v2.behandling.BehandlingDtoV2
import no.nav.bidrag.behandling.service.VirkningstidspunktService
import no.nav.bidrag.behandling.transformers.Dtomapper
import no.nav.bidrag.commons.util.secureLogger
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.RequestBody

private val log = KotlinLogging.logger {}

@BehandlingRestControllerV2
class VirkningstidspunktController(
private val virkningstidspunktService: VirkningstidspunktService,
private val dtomapper: Dtomapper,
) {
@PutMapping("/behandling/{behandlingsid}/opphorsdato")
@Operation(
description = "Oppdatere opphørsdato for behandling.",
security = [SecurityRequirement(name = "bearer-key")],
)
fun oppdatereOpphørsdato(
@PathVariable behandlingsid: Long,
@Valid @RequestBody(required = true) request: OppdaterOpphørsdatoRequestDto,
): BehandlingDtoV2 {
log.info { "Oppdaterer virkningstidspunkt for behandling $behandlingsid" }
secureLogger.info { "Oppdaterer virkningstidspunkt for behandling $behandlingsid med forespørsel $request" }

val behandling = virkningstidspunktService.oppdaterOpphørsdato(behandlingsid, request)

return dtomapper.tilDto(behandling)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,25 @@ data class VirkningstidspunktDto(
@Schema(description = "Saksbehandlers begrunnelse")
val begrunnelse: BegrunnelseDto,
val begrunnelseFraOpprinneligVedtak: BegrunnelseDto? = null,
val opphør: OpphørsdetaljerDto? = null,
) {
@Deprecated("Bruk begrunnelse")
@Schema(description = "Bruk begrunnelse", deprecated = true)
val notat: BegrunnelseDto = begrunnelse
}

data class OpphørsdetaljerDto(
val opphørsdato: LocalDate? = null,
@Schema(description = "Løpende opphørsvedtak detaljer. Er satt hvis det finnes en vedtak hvor bidraget er opphørt")
val eksisterendeOpphør: EksisterendeOpphørsvedtakDto? = null,
) {
data class EksisterendeOpphørsvedtakDto(
val vedtaksid: Long,
val opphørsdato: LocalDate,
val vedtaksdato: LocalDate,
)
}

data class BoforholdValideringsfeil(
val andreVoksneIHusstanden: AndreVoksneIHusstandenPeriodeseringsfeil? = null,
val husstandsmedlem: List<BoforholdPeriodeseringsfeil>? = emptyList(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@file:Suppress("ktlint:standard:filename")

package no.nav.bidrag.behandling.dto.v1.behandling

import java.time.LocalDate

data class OppdaterOpphørsdatoRequestDto(
val opphørsdato: LocalDate? = null,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package no.nav.bidrag.behandling.dto.v1.beregning

import no.nav.bidrag.behandling.dto.v1.beregning.UgyldigBeregningDto.UgyldigResultatPeriode
import no.nav.bidrag.behandling.transformers.finnSluttberegningIReferanser
import no.nav.bidrag.beregn.core.exception.BegrensetRevurderingLikEllerLavereEnnLøpendeBidragException
import no.nav.bidrag.domene.enums.beregning.Resultatkode
import no.nav.bidrag.domene.enums.beregning.Resultatkode.Companion.erDirekteAvslag
import no.nav.bidrag.domene.enums.beregning.Samværsklasse
Expand All @@ -9,25 +12,104 @@ import no.nav.bidrag.transport.behandling.beregning.barnebidrag.BeregnetBarnebid
import no.nav.bidrag.transport.behandling.felles.grunnlag.DelberegningBidragspliktigesAndel
import no.nav.bidrag.transport.behandling.felles.grunnlag.DelberegningUnderholdskostnad
import no.nav.bidrag.transport.behandling.felles.grunnlag.SluttberegningBarnebidrag
import no.nav.bidrag.transport.behandling.felles.grunnlag.innholdTilObjekt
import java.math.BigDecimal
import java.time.YearMonth
import java.time.format.DateTimeFormatter

val YearMonth.formatterDatoFom get() = this.atDay(1).format(DateTimeFormatter.ofPattern("MM.YYYY"))
val YearMonth.formatterDatoTom get() = this.atEndOfMonth().format(DateTimeFormatter.ofPattern("MM.YYYY"))
val ÅrMånedsperiode.periodeString get() = "${fom.formatterDatoFom} - ${til?.formatterDatoTom ?: ""}"

fun BegrensetRevurderingLikEllerLavereEnnLøpendeBidragException.opprettBegrunnelse(): UgyldigBeregningDto {
val allePerioder = data.beregnetBarnebidragPeriodeListe.sortedBy { it.periode.fom }
val perioderUtenForskudd =
allePerioder.filter {
val sluttberegning =
data.grunnlagListe
.finnSluttberegningIReferanser(
it.grunnlagsreferanseListe,
)?.innholdTilObjekt<SluttberegningBarnebidrag>()
sluttberegning?.resultat == SluttberegningBarnebidrag::bidragJustertTilForskuddssats.name &&
it.resultat.beløp == BigDecimal.ZERO
}
val resultatPerioderUtenForskudd =
perioderUtenForskudd.map { periode ->
UgyldigBeregningDto.UgyldigResultatPeriode(
periode = periode.periode,
type = UgyldigBeregningDto.UgyldigBeregningType.BEGRENSET_REVURDERING_UTEN_LØPENDE_FORSKUDD,
)
}
val resultatPerioderUnderLøpendeBidrag =
(periodeListe - perioderUtenForskudd.map { it.periode }).map { periode ->
UgyldigBeregningDto.UgyldigResultatPeriode(
periode = periode,
type = UgyldigBeregningDto.UgyldigBeregningType.BEGRENSET_REVURDERING_LIK_ELLER_LAVERE_ENN_LØPENDE_BIDRAG,
)
}
if (perioderUtenForskudd.isNotEmpty()) {
return UgyldigBeregningDto(
tittel = "Begrenset revurdering",
resultatPeriode = resultatPerioderUtenForskudd + resultatPerioderUnderLøpendeBidrag,
begrunnelse =
if (perioderUtenForskudd.size > 1) {
"Perioder ${perioderUtenForskudd.joinToString {it.periode.periodeString}} har ingen løpende forskudd"
} else {
"Periode ${perioderUtenForskudd.first().periode.periodeString} har ingen løpende forskudd"
},
perioder = perioderUtenForskudd.map { it.periode },
)
}
return UgyldigBeregningDto(
tittel = "Begrenset revurdering",
perioder = this.periodeListe,
resultatPeriode = resultatPerioderUtenForskudd + resultatPerioderUnderLøpendeBidrag,
begrunnelse =
if (this.periodeListe.size > 1) {
"Flere perioder er lik eller lavere enn løpende bidrag"
} else {
"Periode ${this.periodeListe.first().periodeString} er lik eller lavere enn løpende bidrag"
},
)
}

data class ResultatBidragsberegningBarn(
val barn: ResultatRolle,
val resultat: BeregnetBarnebidragResultat,
val avslaskode: Resultatkode? = null,
val ugyldigBeregning: UgyldigBeregningDto? = null,
)

data class UgyldigBeregningDto(
val tittel: String,
val begrunnelse: String,
val resultatPeriode: List<UgyldigResultatPeriode>,
val perioder: List<ÅrMånedsperiode> = emptyList(),
) {
data class UgyldigResultatPeriode(
val periode: ÅrMånedsperiode,
val type: UgyldigBeregningType,
)

enum class UgyldigBeregningType {
BEGRENSET_REVURDERING_LIK_ELLER_LAVERE_ENN_LØPENDE_BIDRAG,
BEGRENSET_REVURDERING_UTEN_LØPENDE_FORSKUDD,
}
}

data class ResultatBidragberegningDto(
val resultatBarn: List<ResultatBidragsberegningBarnDto> = emptyList(),
)

data class ResultatBidragsberegningBarnDto(
val barn: ResultatRolle,
val ugyldigBeregning: UgyldigBeregningDto? = null,
val perioder: List<ResultatBarnebidragsberegningPeriodeDto>,
)

data class ResultatBarnebidragsberegningPeriodeDto(
val periode: ÅrMånedsperiode,
val ugyldigBeregning: UgyldigResultatPeriode? = null,
val underholdskostnad: BigDecimal,
val bpsAndelU: BigDecimal,
val bpsAndelBeløp: BigDecimal,
Expand All @@ -42,6 +124,13 @@ data class ResultatBarnebidragsberegningPeriodeDto(
val resultatkodeVisningsnavn get() =
if (resultatKode?.erDirekteAvslag() == true) {
resultatKode.visningsnavnIntern()
} else if (ugyldigBeregning != null) {
when (ugyldigBeregning.type) {
UgyldigBeregningDto.UgyldigBeregningType.BEGRENSET_REVURDERING_LIK_ELLER_LAVERE_ENN_LØPENDE_BIDRAG,
-> "Lavere enn løpende bidrag"
UgyldigBeregningDto.UgyldigBeregningType.BEGRENSET_REVURDERING_UTEN_LØPENDE_FORSKUDD,
-> "Ingen løpende forskudd"
}
} else {
beregningsdetaljer
?.sluttberegning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import no.nav.bidrag.behandling.database.datamodell.hentNavn
import no.nav.bidrag.behandling.dto.v1.beregning.ResultatBidragsberegningBarn
import no.nav.bidrag.behandling.dto.v1.beregning.ResultatForskuddsberegningBarn
import no.nav.bidrag.behandling.dto.v1.beregning.ResultatRolle
import no.nav.bidrag.behandling.dto.v1.beregning.opprettBegrunnelse
import no.nav.bidrag.behandling.transformers.beregning.validerForSærbidrag
import no.nav.bidrag.behandling.transformers.finnDelberegningBPsBeregnedeTotalbidrag
import no.nav.bidrag.behandling.transformers.vedtak.mapping.tilvedtak.VedtakGrunnlagMapper
Expand All @@ -18,6 +19,7 @@ import no.nav.bidrag.beregn.core.bo.SjablonInnhold
import no.nav.bidrag.beregn.core.bo.SjablonNøkkel
import no.nav.bidrag.beregn.core.bo.SjablonPeriode
import no.nav.bidrag.beregn.core.dto.SjablonPeriodeCore
import no.nav.bidrag.beregn.core.exception.BegrensetRevurderingLikEllerLavereEnnLøpendeBidragException
import no.nav.bidrag.beregn.core.service.mapper.CoreMapper
import no.nav.bidrag.beregn.forskudd.BeregnForskuddApi
import no.nav.bidrag.beregn.særbidrag.BeregnSærbidragApi
Expand Down Expand Up @@ -120,10 +122,10 @@ class BeregningService(
return if (mapper.validering.run { behandling.erDirekteAvslagUtenBeregning() }) {
behandling.søknadsbarn.map { behandling.tilResultatAvslagBidrag(it) }
} else {
try {
behandling.søknadsbarn.map { søknasdbarn ->
val grunnlagBeregning =
mapper.byggGrunnlagForBeregning(behandling, søknasdbarn)
behandling.søknadsbarn.map { søknasdbarn ->
val grunnlagBeregning =
mapper.byggGrunnlagForBeregning(behandling, søknasdbarn)
try {
ResultatBidragsberegningBarn(
søknasdbarn.mapTilResultatBarn(),
beregnBarnebidragApi.beregn(grunnlagBeregning).let {
Expand All @@ -136,10 +138,23 @@ class BeregningService(
)
},
)
} catch (e: BegrensetRevurderingLikEllerLavereEnnLøpendeBidragException) {
ResultatBidragsberegningBarn(
ugyldigBeregning = e.opprettBegrunnelse(),
barn = søknasdbarn.mapTilResultatBarn(),
resultat =
e.data.copy(
grunnlagListe =
(e.data.grunnlagListe + grunnlagBeregning.grunnlagListe)
.toSet()
.toList()
.fjernMidlertidligPersonobjekterBMsbarn(),
),
)
} catch (e: Exception) {
LOGGER.warn(e) { "Det skjedde en feil ved beregning av barnebidrag: ${e.message}" }
throw HttpClientErrorException(HttpStatus.BAD_REQUEST, e.message!!)
}
} catch (e: Exception) {
LOGGER.warn(e) { "Det skjedde en feil ved beregning av barnebidrag: ${e.message}" }
throw HttpClientErrorException(HttpStatus.BAD_REQUEST, e.message!!)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ import no.nav.bidrag.domene.enums.behandling.TypeBehandling
import no.nav.bidrag.domene.enums.rolle.Rolletype
import no.nav.bidrag.domene.enums.vedtak.Stønadstype
import no.nav.bidrag.domene.enums.vedtak.Vedtakstype
import no.nav.bidrag.domene.sak.Saksnummer
import no.nav.bidrag.transport.behandling.stonad.request.HentStønadHistoriskRequest
import no.nav.bidrag.transport.behandling.stonad.request.LøpendeBidragssakerRequest
import no.nav.bidrag.transport.felles.commonObjectmapper
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.web.client.HttpClientErrorException
import java.time.LocalDate
import java.time.LocalDateTime

private val log = KotlinLogging.logger {}

Expand Down Expand Up @@ -49,8 +52,8 @@ class ValiderBehandlingService(
if (request.vedtakstype == Vedtakstype.KLAGE || request.harReferanseTilAnnenBehandling) {
return "Kan ikke behandle klage eller omgjøring"
}
if (request.vedtakstype == Vedtakstype.REVURDERING || request.søknadstype == BisysSøknadstype.BEGRENSET_REVURDERING) {
return "Kan ikke behandle begrenset revurdering"
if (!kanBehandleBegrensetRevurdering(request)) {
return "Kan ikke behandle begrenset revurdering. Minst en løpende forskudd eller bidrag periode har utenlandsk valuta"
}
val bp = request.bidragspliktig
if (bp == null || bp.erUkjent == true || bp.ident == null) return "Behandlingen mangler bidragspliktig"
Expand All @@ -59,7 +62,11 @@ class ValiderBehandlingService(
.hentAlleStønaderForBidragspliktig(bp.ident)
.stønader
.any { it.type != Stønadstype.FORSKUDD }
if (harBPMinstEnBidragsstønad) return "Bidragspliktig har en eller flere historiske eller løpende bidrag"
if (harBPMinstEnBidragsstønad &&
!request.erBegrensetRevurdering()
) {
return "Bidragspliktig har en eller flere historiske eller løpende bidrag"
}

if (request.søktFomDato != null && request.søktFomDato.isBefore(LocalDate.parse("2023-03-01"))) {
return "Behandlingen er registrert med søkt fra dato før mars 2023"
Expand All @@ -82,4 +89,37 @@ class ValiderBehandlingService(
)
}
}

fun kanBehandleBegrensetRevurdering(request: KanBehandlesINyLøsningRequest): Boolean =
if (request.erBegrensetRevurdering()) {
harIngenHistoriskePerioderMedUtenlandskValuta(request, Stønadstype.BIDRAG) &&
harIngenHistoriskePerioderMedUtenlandskValuta(request, Stønadstype.FORSKUDD)
} else {
true
}

private fun KanBehandlesINyLøsningRequest.erBegrensetRevurdering() =
this.søknadstype == BisysSøknadstype.BEGRENSET_REVURDERING ||
this.søknadstype == BisysSøknadstype.REVURDERING

private fun harIngenHistoriskePerioderMedUtenlandskValuta(
request: KanBehandlesINyLøsningRequest,
stønadstype: Stønadstype,
): Boolean =
request.søknadsbarn.filter { it.ident != null }.all {
bidragStonadConsumer
.hentHistoriskeStønader(
HentStønadHistoriskRequest(
type = stønadstype,
sak = Saksnummer(request.saksnummer),
skyldner = request.bidragspliktig!!.ident!!,
kravhaver = it.ident!!,
gyldigTidspunkt = LocalDateTime.now(),
),
)?.let {
it.periodeListe.all {
it.valutakode == "NOK" || it.valutakode.isNullOrEmpty()
}
} != false
}
}
Loading
Loading