From 2d5c25e6f758e9a1ae2023db50ff794e48a479ba Mon Sep 17 00:00:00 2001 From: Ken Gullaksen Date: Thu, 7 Mar 2024 11:14:12 +0100 Subject: [PATCH] =?UTF-8?q?Sykefrav=C3=A6rstatistikk=20m=C3=A5=20ta=20hens?= =?UTF-8?q?yn=20til=20=C3=A5r=20og=20kvartal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dersom det blir gjort en reeksport av gammel statistikk så blir disse liggende som siste melding på kafka. Dette skjedde i prod i februar. Denne PR fikser problemet uten å dra ned eksisterende endepunkt som vi vet viser riktig data pga full eksport gjort 29. feb. Det kommer en ny PR straks som tar i bruk nye tabeller og rydder opp koden. Dette for å unngå nedetid mens vi fyller tabellen. --- .../Sykefrav\303\246rstatistikkController.kt" | 35 +++ .../Sykefrav\303\246rstatistikkRepository.kt" | 160 +++++++++----- ...__arstall_kvartal_sykefravarstatistikk.sql | 19 ++ ...frav\303\246rstatistikkIntegrationTest.kt" | 204 ++++++++++++++++-- 4 files changed, 338 insertions(+), 80 deletions(-) create mode 100644 src/main/resources/db/migration/V12__arstall_kvartal_sykefravarstatistikk.sql diff --git "a/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkController.kt" "b/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkController.kt" index 6614bc51..da5c0b50 100644 --- "a/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkController.kt" +++ "b/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkController.kt" @@ -22,6 +22,7 @@ class SykefraværstatistikkController( other = { "1" }, ) + // TODO: gjør v2 om til default når ajour @GetMapping("/api/sykefravaerstatistikk/{orgnr}") fun getStatistikk( @PathVariable orgnr: String, @@ -30,6 +31,40 @@ class SykefraværstatistikkController( .hentOrganisasjonerBasertPaRettigheter(authenticatedUserHolder.fnr, tjenestekode, tjenesteversjon) .firstOrNull { it.organizationNumber == orgnr } + return if (org != null) { + val statistikk = sykefraværstatistikkRepository.virksomhetstatistikk_v1(orgnr)?.let { + StatistikkRespons( + type = it.kategori, + label = org.name ?: it.kode, + prosent = it.prosent, + ) + } ?: sykefraværstatistikkRepository.statistikk_v1(orgnr)?.let { + StatistikkRespons( + type = it.kategori, + label = it.kode, + prosent = it.prosent, + ) + } + statistikk.asResponseEntity() + } else { + sykefraværstatistikkRepository.statistikk_v1(orgnr)?.let { + StatistikkRespons( + type = it.kategori, + label = it.kode, + prosent = it.prosent, + ) + }.asResponseEntity() + } + } + + @GetMapping("/api/sykefravaerstatistikk_v2/{orgnr}") + fun getStatistikkV2( + @PathVariable orgnr: String, + ): ResponseEntity { + val org = altinnService + .hentOrganisasjonerBasertPaRettigheter(authenticatedUserHolder.fnr, tjenestekode, tjenesteversjon) + .firstOrNull { it.organizationNumber == orgnr } + return if (org != null) { val statistikk = sykefraværstatistikkRepository.virksomhetstatistikk(orgnr)?.let { StatistikkRespons( diff --git "a/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkRepository.kt" "b/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkRepository.kt" index 378ffa36..6107edf9 100644 --- "a/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkRepository.kt" +++ "b/src/main/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkRepository.kt" @@ -6,13 +6,11 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.ObjectMapper import org.apache.kafka.clients.consumer.ConsumerRecord import org.springframework.context.annotation.Profile -import org.springframework.jdbc.core.JdbcTemplate import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate import org.springframework.kafka.annotation.KafkaListener import org.springframework.stereotype.Repository import org.springframework.stereotype.Service import java.math.BigDecimal -import java.sql.PreparedStatement data class Statistikk( val kategori: String, @@ -22,82 +20,122 @@ data class Statistikk( @Repository class SykefraværstatistikkRepository( - private val jdbcTemplate: JdbcTemplate, private val namedParameterJdbcTemplate: NamedParameterJdbcTemplate, ) { - fun virksomhetstatistikk(virksomhetsnummer: String): Statistikk? { - return namedParameterJdbcTemplate.queryForList( - """ - select kategori, kode, prosent - from sykefraværstatistikk - where kategori = 'VIRKSOMHET' and kode = :virksomhetsnummer - """.trimIndent(), - mapOf("virksomhetsnummer" to virksomhetsnummer) - ).firstOrNull()?.let { - Statistikk( - kategori = it["kategori"] as String, - kode = it["kode"] as String, - prosent = (it["prosent"] as BigDecimal).toDouble(), - ) - } + // TODO: fjern når "v2" er ajour + fun virksomhetstatistikk_v1(virksomhetsnummer: String) = namedParameterJdbcTemplate.queryForList( + """ + select kategori, kode, prosent + from sykefraværstatistikk + where kategori = 'VIRKSOMHET' and kode = :virksomhetsnummer + """.trimIndent(), + mapOf("virksomhetsnummer" to virksomhetsnummer) + ).firstOrNull()?.let { + Statistikk( + kategori = it["kategori"] as String, + kode = it["kode"] as String, + prosent = (it["prosent"] as BigDecimal).toDouble(), + ) } - fun statistikk(virksomhetsnummer: String): Statistikk? { - return namedParameterJdbcTemplate.queryForList( - """ -- TODO: vurder eksplisitte felt i stedet for coalesce her, kan by på problemer - select coalesce(sb.kategori, sn.kategori) as kategori, coalesce(sb.kode, sn.kode) as kode, coalesce(sb.prosent, sn.prosent) as prosent - from sykefraværstatistikk_metadata meta - left join sykefraværstatistikk sb on sb.kategori = 'BRANSJE' and sb.kode = meta.bransje - left join sykefraværstatistikk sn on sn.kategori = 'NÆRING' and sn.kode = meta.næring - where meta.virksomhetsnummer = :virksomhetsnummer - and coalesce(sb.prosent, sn.prosent) is not null - order by coalesce(sb.kategori, sn.kategori) - """.trimIndent(), - mapOf("virksomhetsnummer" to virksomhetsnummer) - ).firstOrNull()?.let { - Statistikk( - kategori = it["kategori"] as String, - kode = it["kode"] as String, - prosent = (it["prosent"] as BigDecimal).toDouble(), - ) - } + // TODO: fjern når "v2" er ajour + fun statistikk_v1(virksomhetsnummer: String) = namedParameterJdbcTemplate.queryForList( + """ -- TODO: vurder eksplisitte felt i stedet for coalesce her, kan by på problemer + select coalesce(sb.kategori, sn.kategori) as kategori, coalesce(sb.kode, sn.kode) as kode, coalesce(sb.prosent, sn.prosent) as prosent + from sykefraværstatistikk_metadata meta + left join sykefraværstatistikk sb on sb.kategori = 'BRANSJE' and sb.kode = meta.bransje + left join sykefraværstatistikk sn on sn.kategori = 'NÆRING' and sn.kode = meta.næring + where meta.virksomhetsnummer = :virksomhetsnummer + and coalesce(sb.prosent, sn.prosent) is not null + order by coalesce(sb.kategori, sn.kategori) + """.trimIndent(), + mapOf("virksomhetsnummer" to virksomhetsnummer) + ).firstOrNull()?.let { + Statistikk( + kategori = it["kategori"] as String, + kode = it["kode"] as String, + prosent = (it["prosent"] as BigDecimal).toDouble(), + ) + } + + fun virksomhetstatistikk(virksomhetsnummer: String) = namedParameterJdbcTemplate.queryForList( + """ + select kategori, kode, prosent + from sykefraværstatistikk_v2 + where kategori = 'VIRKSOMHET' and kode = :virksomhetsnummer + order by arstall desc, kvartal desc + """.trimIndent(), + mapOf("virksomhetsnummer" to virksomhetsnummer) + ).firstOrNull()?.let { + Statistikk( + kategori = it["kategori"] as String, + kode = it["kode"] as String, + prosent = (it["prosent"] as BigDecimal).toDouble(), + ) + } + fun statistikk(virksomhetsnummer: String) = namedParameterJdbcTemplate.queryForList( + """ -- TODO: vurder eksplisitte felt i stedet for coalesce her, kan by på problemer + select + coalesce(sb.kategori, sn.kategori) as kategori, + coalesce(sb.kode, sn.kode) as kode, + coalesce(sb.prosent, sn.prosent) as prosent + from sykefraværstatistikk_metadata_v2 meta + left join sykefraværstatistikk_v2 sb on (sb.kode = meta.bransje and sb.arstall = meta.arstall and sb.kvartal = meta.kvartal) + left join sykefraværstatistikk_v2 sn on (sn.kode = meta.næring and sn.arstall = meta.arstall and sn.kvartal = meta.kvartal) + where meta.virksomhetsnummer = :virksomhetsnummer + and coalesce(sb.prosent, sn.prosent) is not null + order by meta.arstall desc, meta.kvartal desc, kategori + """.trimIndent(), + mapOf("virksomhetsnummer" to virksomhetsnummer) + ).firstOrNull()?.let { + Statistikk( + kategori = it["kategori"] as String, + kode = it["kode"] as String, + prosent = (it["prosent"] as BigDecimal).toDouble(), + ) } fun processMetadataVirksomhet(metadata: MetadataVirksomhetDto) { - jdbcTemplate.update( + namedParameterJdbcTemplate.update( """ - insert into sykefraværstatistikk_metadata(virksomhetsnummer, bransje, næring) - values(?, ?, ?) - on conflict (virksomhetsnummer) + insert into sykefraværstatistikk_metadata_v2(virksomhetsnummer, bransje, næring, arstall, kvartal) + values(:virksomhetsnummer, :bransje, :næring, :arstall, :kvartal) + on conflict (virksomhetsnummer, arstall, kvartal) do update set virksomhetsnummer = EXCLUDED.virksomhetsnummer, bransje = EXCLUDED.bransje, næring = EXCLUDED.næring; - """.trimIndent() - ) { ps: PreparedStatement -> - ps.setString(1, metadata.virksomhetsnummer) - ps.setString(2, metadata.bransje) - ps.setString(3, metadata.næring) - } + """.trimIndent(), + mapOf( + "virksomhetsnummer" to metadata.virksomhetsnummer, + "bransje" to metadata.bransje, + "næring" to metadata.næring, + "arstall" to metadata.arstall, + "kvartal" to metadata.kvartal, + ) + ) } fun processStatistikkategori(statistikkategori: StatistikkategoriDto) { - jdbcTemplate.update( + namedParameterJdbcTemplate.update( """ - insert into sykefraværstatistikk(kode, kategori, prosent) - values(?, ?, ?) - on conflict (kode) + insert into sykefraværstatistikk_v2(kode, kategori, prosent, arstall, kvartal) + values(:kode, :kategori, :prosent, :arstall, :kvartal) + on conflict (kode, arstall, kvartal) do update set kode = EXCLUDED.kode, kategori = EXCLUDED.kategori, prosent = EXCLUDED.prosent; - """.trimIndent() - ) { ps: PreparedStatement -> - ps.setString(1, statistikkategori.kode) - ps.setString(2, statistikkategori.kategori) - ps.setDouble(3, statistikkategori.prosent) - } + """.trimIndent(), + mapOf( + "kode" to statistikkategori.kode, + "kategori" to statistikkategori.kategori, + "prosent" to statistikkategori.prosent, + "arstall" to statistikkategori.siste4Kvartal?.arstall, + "kvartal" to statistikkategori.siste4Kvartal?.kvartal, + ) + ) } } @@ -110,7 +148,7 @@ class SykefraværstatistikkKafkaListener( ) { @Profile("dev-gcp", "prod-gcp") @KafkaListener( - id = "min-side-arbeidsgiver-sfmeta-1", + id = "min-side-arbeidsgiver-sfmeta-2", topics = ["arbeidsgiver.sykefravarsstatistikk-metadata-virksomhet-v1"], containerFactory = "errorLoggingKafkaListenerContainerFactory" ) @@ -121,7 +159,7 @@ class SykefraværstatistikkKafkaListener( @Profile("dev-gcp", "prod-gcp") @KafkaListener( - id = "min-side-arbeidsgiver-sfstats-1", + id = "min-side-arbeidsgiver-sfstats-2", topics = [ "arbeidsgiver.sykefravarsstatistikk-virksomhet-v1", "arbeidsgiver.sykefravarsstatistikk-naring-v1", @@ -144,6 +182,8 @@ data class MetadataVirksomhetDto @JsonCreator(mode = JsonCreator.Mode.PROPERTIES @param:JsonProperty("orgnr") val virksomhetsnummer: String, @param:JsonProperty("naring") val næring: String, @param:JsonProperty("bransje") val bransje: String?, + @param:JsonProperty("arstall") val arstall: Number, + @param:JsonProperty("kvartal") val kvartal: Number, ) /** @@ -162,5 +202,7 @@ data class StatistikkategoriDto @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) @JsonIgnoreProperties(ignoreUnknown = true) data class ProsentWrapper @JsonCreator(mode = JsonCreator.Mode.PROPERTIES) constructor( @param:JsonProperty("prosent") val prosent: Double, + @param:JsonProperty("arstall") val arstall: Number, + @param:JsonProperty("kvartal") val kvartal: Number, ) } diff --git a/src/main/resources/db/migration/V12__arstall_kvartal_sykefravarstatistikk.sql b/src/main/resources/db/migration/V12__arstall_kvartal_sykefravarstatistikk.sql new file mode 100644 index 00000000..fd3b3d40 --- /dev/null +++ b/src/main/resources/db/migration/V12__arstall_kvartal_sykefravarstatistikk.sql @@ -0,0 +1,19 @@ +create table sykefraværstatistikk_metadata_v2 +( + virksomhetsnummer text not null, + arstall numeric not null, + kvartal numeric not null, + bransje text, + næring text not null, + primary key (virksomhetsnummer, arstall, kvartal) +); + +create table sykefraværstatistikk_v2 +( + kode text not null, + arstall numeric not null, + kvartal numeric not null, + kategori text not null, + prosent decimal not null, + primary key (kode, arstall, kvartal) +); \ No newline at end of file diff --git "a/src/test/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkIntegrationTest.kt" "b/src/test/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkIntegrationTest.kt" index 4f621cab..55aa0af7 100644 --- "a/src/test/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkIntegrationTest.kt" +++ "b/src/test/kotlin/no/nav/arbeidsgiver/min_side/sykefrav\303\246rstatistikk/Sykefrav\303\246rstatistikkIntegrationTest.kt" @@ -68,10 +68,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "123", "kategori": "VIRKSOMHET", "sistePubliserteKvartal": { - "prosent": 3.15 + "prosent": 3.15, + "arstall": "2023", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.14 + "prosent": 3.14, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "123", + "kategori": "VIRKSOMHET", + "sistePubliserteKvartal": { + "prosent": 2.15, + "arstall": "2022", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 2.14, + "arstall": "2022", + "kvartal": "1" } } """ @@ -80,7 +102,7 @@ class SykefraværstatistikkIntegrationTest { mockMvc .perform( - get("/api/sykefravaerstatistikk/{orgnr}", "123") + get("/api/sykefravaerstatistikk_v2/{orgnr}", "123") .with(jwtWithPid("42")) ) .andExpect(status().isOk) @@ -107,7 +129,20 @@ class SykefraværstatistikkIntegrationTest { { "orgnr": "123", "bransje": "Testing", - "naring": "IT" + "naring": "IT", + "arstall": "2023", + "kvartal": "1" + } + """ + ) + processMetadataVirksomhet( + """ + { + "orgnr": "123", + "bransje": "Testing Gammel", + "naring": "IT Gammel", + "arstall": "2022", + "kvartal": "1" } """ ) @@ -117,10 +152,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "Testing", "kategori": "BRANSJE", "sistePubliserteKvartal": { - "prosent": 3.15 + "prosent": 3.15, + "arstall": "2023", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.14 + "prosent": 3.14, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "Testing Gammel", + "kategori": "BRANSJE", + "sistePubliserteKvartal": { + "prosent": 2.15, + "arstall": "2022", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 2.14, + "arstall": "2022", + "kvartal": "1" } } """ @@ -131,10 +188,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "IT", "kategori": "NÆRING", "sistePubliserteKvartal": { - "prosent": 3.16 + "prosent": 3.16, + "arstall": "2023", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 3.17, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "IT Gammel", + "kategori": "NÆRING", + "sistePubliserteKvartal": { + "prosent": 2.16, + "arstall": "2022", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.17 + "prosent": 2.17, + "arstall": "2022", + "kvartal": "1" } } """ @@ -143,7 +222,7 @@ class SykefraværstatistikkIntegrationTest { mockMvc .perform( - get("/api/sykefravaerstatistikk/{orgnr}", "123") + get("/api/sykefravaerstatistikk_v2/{orgnr}", "123") .with(jwtWithPid("42")) ) .andExpect(status().isOk) @@ -170,7 +249,9 @@ class SykefraværstatistikkIntegrationTest { { "orgnr": "123", "bransje": "Testing", - "naring": "IT" + "naring": "IT", + "arstall": "2023", + "kvartal": "1" } """ ) @@ -180,10 +261,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "IT", "kategori": "NÆRING", "sistePubliserteKvartal": { - "prosent": 3.15 + "prosent": 3.15, + "arstall": "2023", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.14 + "prosent": 3.14, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "IT", + "kategori": "NÆRING", + "sistePubliserteKvartal": { + "prosent": 2.15, + "arstall": "2022", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 2.14, + "arstall": "2022", + "kvartal": "1" } } """ @@ -192,7 +295,7 @@ class SykefraværstatistikkIntegrationTest { mockMvc .perform( - get("/api/sykefravaerstatistikk/{orgnr}", "123") + get("/api/sykefravaerstatistikk_v2/{orgnr}", "123") .with(jwtWithPid("42")) ) .andExpect(status().isOk) @@ -221,7 +324,20 @@ class SykefraværstatistikkIntegrationTest { { "orgnr": "123", "bransje": "Testing", - "naring": "IT" + "naring": "IT", + "arstall": "2023", + "kvartal": "1" + } + """ + ) + processMetadataVirksomhet( + """ + { + "orgnr": "123", + "bransje": "Testing Gammel", + "naring": "IT", + "arstall": "2022", + "kvartal": "1" } """ ) @@ -231,10 +347,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "Testing", "kategori": "BRANSJE", "sistePubliserteKvartal": { - "prosent": 3.15 + "prosent": 3.15, + "arstall": "2023", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.14 + "prosent": 3.14, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "Testing Gammel", + "kategori": "BRANSJE", + "sistePubliserteKvartal": { + "prosent": 2.15, + "arstall": "2022", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 2.14, + "arstall": "2022", + "kvartal": "1" } } """ @@ -245,10 +383,32 @@ class SykefraværstatistikkIntegrationTest { "kode": "IT", "kategori": "NÆRING", "sistePubliserteKvartal": { - "prosent": 3.16 + "prosent": 3.16, + "arstall": "2023", + "kvartal": "1" + }, + "siste4Kvartal": { + "prosent": 3.17, + "arstall": "2023", + "kvartal": "1" + } + } + """ + ) + processStatistikkkategori( + """ + { + "kode": "IT Gammel", + "kategori": "NÆRING", + "sistePubliserteKvartal": { + "prosent": 2.16, + "arstall": "2022", + "kvartal": "1" }, "siste4Kvartal": { - "prosent": 3.17 + "prosent": 2.17, + "arstall": "2022", + "kvartal": "1" } } """ @@ -257,7 +417,7 @@ class SykefraværstatistikkIntegrationTest { mockMvc .perform( - get("/api/sykefravaerstatistikk/{orgnr}", "123") + get("/api/sykefravaerstatistikk_v2/{orgnr}", "123") .with(jwtWithPid("42")) ) .andExpect(status().isOk) @@ -284,7 +444,9 @@ class SykefraværstatistikkIntegrationTest { { "orgnr": "123", "bransje": "Testing", - "naring": "IT" + "naring": "IT", + "arstall": "2023", + "kvartal": "1" } """ ) @@ -292,7 +454,7 @@ class SykefraværstatistikkIntegrationTest { mockMvc .perform( - get("/api/sykefravaerstatistikk/{orgnr}", "123") + get("/api/sykefravaerstatistikk_v2/{orgnr}", "123") .with(jwtWithPid("42")) ) .andExpect(status().isNoContent)