From f0a0d5f7f36bf8aa56cde1362a78a9fab0894ec3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Tue, 16 Jun 2026 07:27:10 +0200 Subject: [PATCH 01/11] =?UTF-8?q?Legg=20til=20BestillingProgressDTO=20og?= =?UTF-8?q?=20forbedre=20DashboardService=20med=20feilstatush=C3=A5ndterin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/dto/BestillingProgressDTO.java | 105 ++++++++++++++++++ .../dolly/provider/DashboardController.java | 8 ++ .../BestillingProgressRepository.java | 11 ++ .../nav/dolly/service/DashboardService.java | 42 +++++++ 4 files changed, 166 insertions(+) create mode 100644 apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java new file mode 100644 index 00000000000..39963cff1ad --- /dev/null +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java @@ -0,0 +1,105 @@ +package no.nav.dolly.domain.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import no.nav.dolly.domain.jpa.Testident.Master; + +import java.io.Serializable; +import java.time.LocalDate; + +import static org.apache.commons.lang3.StringUtils.EMPTY; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BestillingProgressDTO implements Serializable { + + private LocalDate sistOppdatert; + + private Long bestillingId; + + private String ident; + private Master master; + + private String aaregStatus; + private String arbeidsplassenCVStatus; + private String arbeidssoekerregisteretStatus; + private String arenaforvalterStatus; + private String brregstubStatus; + private String dokarkivStatus; + private String etterlatteStatus; + private String feil; + private String fullmaktStatus; + private String histarkStatus; + private String inntektsmeldingStatus; + private String inntektstubStatus; + private String instdataStatus; + private String kelvinAapStatus; + private String kontoregisterStatus; + private String krrstubStatus; + private String medlStatus; + private String nomStatus; + private String pdlForvalterStatus; + private String pdlImportStatus; + private String pdlOrdreStatus; + private String pdlPersonStatus; + private String pensjonforvalterStatus; + private String sigrunstubStatus; + private String skattekortStatus; + private String skjermingsregisterStatus; + private String sykemeldingStatus; + private String udistubStatus; + private String yrkesskadeStatus; + + @Override + public String toString() { + return "{" + + "\"sistOppdatert\":" + sistOppdatert + + ", \"bestillingId\":" + bestillingId + + getError("ident", ident) + + getError("master" , master.toString()) + + getError("aaregStatus" , aaregStatus) + + getError("arbeidsplassenCVStatus" , arbeidsplassenCVStatus) + + getError("arbeidssoekerregisteretStatus" , arbeidssoekerregisteretStatus) + + getError("arenaforvalterStatus" , arenaforvalterStatus) + + getError("brregstubStatus" , brregstubStatus) + + getError("dokarkivStatus" , dokarkivStatus) + + getError("etterlatteStatus" , etterlatteStatus) + + getError("feil" , feil) + + getError("fullmaktStatus" , fullmaktStatus) + + getError("histarkStatus" , histarkStatus) + + getError("inntektsmeldingStatus" , inntektsmeldingStatus) + + getError("inntektstubStatus" , inntektstubStatus) + + getError("instdataStatus" , instdataStatus) + + getError("kelvinAapStatus" , kelvinAapStatus) + + getError("kontoregisterStatus" , kontoregisterStatus) + + getError("krrstubStatus" , krrstubStatus) + + getError("medlStatus" , medlStatus) + + getError("nomStatus" , nomStatus) + + getError("pdlForvalterStatus" , pdlForvalterStatus) + + getError("pdlImportStatus" , pdlImportStatus) + + getError("pdlOrdreStatus" , isNotBlank(pdlOrdreStatus) ? + pdlOrdreStatus.substring(16) : EMPTY) + + getError("pdlPersonStatus" , pdlPersonStatus) + + getError("pensjonforvalterStatus" , pensjonforvalterStatus) + + getError("sigrunstubStatus" , sigrunstubStatus) + + getError("skattekortStatus" , skattekortStatus) + + getError("skjermingsregisterStatus" , skjermingsregisterStatus) + + getError("sykemeldingStatus" , sykemeldingStatus) + + getError("udistubStatus" , udistubStatus) + + getError("yrkesskadeStatus" , yrkesskadeStatus) + + "}"; + } + + private static String getError(String kolonnenavn, String kolonneverdi) { + + return isNotBlank(kolonneverdi) && kolonneverdi.toLowerCase().contains("feil") ? + ", \"%s\":\"%s\"".formatted(kolonnenavn, kolonneverdi) : ""; + } +} diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java index 79380472a02..effd6792c35 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java @@ -11,6 +11,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; +import tools.jackson.databind.JsonNode; @RequestMapping("/api/v1/dashboard") @RestController @@ -26,6 +27,13 @@ public Flux getDashboardPersoner() { return dashboardService.getPersonerStatus(); } + @GetMapping(value = "/feil") + @Operation(description = "Henter feilstatus for personer opprettet.") + public Flux getDashboardFeil() { + + return dashboardService.getFeilstatus(); + } + @GetMapping(value = "/teams") @Operation(description = "Henter status per team i hht Teamkatalogen og antall unike personer som har bestilt. Gjelder AZURE-brukere.") public Flux getDashboardTeams() { diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java index 06218996c77..5fa576ea95d 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java @@ -52,4 +52,15 @@ where bp.bestilling_id in (select b.id @Query(value = "select * from bestilling_progress where id = :id for update") Mono findByIdAndLock(@Param("id") Long id); + + @Query(""" + SELECT distinct column_name + FROM information_schema.columns + WHERE table_name = 'bestilling_progress' + AND column_name LIKE '%status%' + AND column_name NOT LIKE 'tps_messaging_status' + OR column_name = 'feil' + ORDER BY column_name; + """) + Flux findStatusColumns(); } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 991fe9f23c3..3a18cd60c40 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -8,29 +8,38 @@ import no.nav.dolly.consumer.brukerservice.dto.BrukerDTO; import no.nav.dolly.consumer.teamkatalog.TeamkatalogConsumer; import no.nav.dolly.consumer.teamkatalog.dto.TeamkatalogDTO; +import no.nav.dolly.domain.dto.BestillingProgressDTO; import no.nav.dolly.domain.dto.DashboardDollyTeamsDTO; import no.nav.dolly.domain.dto.DashboardOrganisasjonerDTO; import no.nav.dolly.domain.dto.DashboardPersonerDTO; import no.nav.dolly.domain.dto.DashboardTeamsDTO; +import no.nav.dolly.domain.jpa.BestillingProgress; import no.nav.dolly.domain.jpa.Bruker; import no.nav.dolly.domain.projection.BestillingerFragment; import no.nav.dolly.domain.projection.DollyTeam2Fragment; import no.nav.dolly.domain.projection.DollyTeamFragment; import no.nav.dolly.domain.projection.OrganisasjonFragment; import no.nav.dolly.domain.projection.TeamFragment; +import no.nav.dolly.repository.BestillingProgressRepository; import no.nav.dolly.repository.BestillingRepository; import no.nav.dolly.repository.BrukerRepository; import no.nav.dolly.repository.TeamRepository; +import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.util.function.Tuples; +import tools.jackson.core.JacksonException; +import tools.jackson.databind.JsonNode; +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.json.JsonMapper; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -47,8 +56,11 @@ public class DashboardService { private final Altinn3TilgangServiceConsumer altinn3TilgangServiceConsumer; private final BestillingRepository bestillingRepository; + private final BestillingProgressRepository bestillingProgressRepository; private final BrukerRepository brukerRepository; private final BrukerServiceConsumer brukerServiceConsumer; + private final R2dbcEntityTemplate entityTemplate; + private final JsonMapper jsonMapper; private final TeamkatalogConsumer teamkatalogConsumer; private final TeamRepository teamRepository; @@ -200,4 +212,34 @@ private static DashboardDollyTeamsDTO.Entry toDollyTeamEntry(DollyTeamFragment f var info = fragment.getInformasjon().split("\\|"); return new DashboardDollyTeamsDTO.Entry(info[0], info[1], toIntExact(oppslag.get(info[2]))); } + + public Flux getFeilstatus() { + + return bestillingProgressRepository.findStatusColumns() + .reduce(new StringBuilder(), (StringBuilder sb, String column) -> + sb.append(" or lower(") + .append(column) + .append(") like '%feil%'")) + .map(query -> new StringBuilder("select * from bestilling_progress where ") + .append(query.substring(4)) + .toString()) + .flatMapMany(query -> entityTemplate.getDatabaseClient() + .sql(query) + .map((row, metadata) -> entityTemplate.getConverter() + .read(BestillingProgressDTO.class, row, metadata)) + .all()) + .sort(Comparator.comparing(BestillingProgressDTO::getBestillingId).reversed()) + .map(BestillingProgressDTO::toString) + .mapNotNull(this::toJson); + } + + private JsonNode toJson(String json) { + + try { + return jsonMapper.readTree(json); + } catch (JacksonException e) { + log.error("Feil ved parsing av JSON string: {}", json, e); + return null; + } + } } From 4e0e2c7d1b0d1f00ffb7b727f0fbe47174e687cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Tue, 16 Jun 2026 13:10:03 +0200 Subject: [PATCH 02/11] Forbedre BestillingProgressDTO med korrekt JSON-format for ident og master --- .../java/no/nav/dolly/domain/dto/BestillingProgressDTO.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java index 39963cff1ad..16ee170f88e 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java @@ -62,8 +62,8 @@ public String toString() { return "{" + "\"sistOppdatert\":" + sistOppdatert + ", \"bestillingId\":" + bestillingId + - getError("ident", ident) + - getError("master" , master.toString()) + + ", \"ident\":\"" + ident + "\"" + + ", \"master\":\"" + master.toString() + "\"" + getError("aaregStatus" , aaregStatus) + getError("arbeidsplassenCVStatus" , arbeidsplassenCVStatus) + getError("arbeidssoekerregisteretStatus" , arbeidssoekerregisteretStatus) + @@ -85,7 +85,7 @@ public String toString() { getError("pdlForvalterStatus" , pdlForvalterStatus) + getError("pdlImportStatus" , pdlImportStatus) + getError("pdlOrdreStatus" , isNotBlank(pdlOrdreStatus) ? - pdlOrdreStatus.substring(16) : EMPTY) + + pdlOrdreStatus.substring(15) : EMPTY) + getError("pdlPersonStatus" , pdlPersonStatus) + getError("pensjonforvalterStatus" , pensjonforvalterStatus) + getError("sigrunstubStatus" , sigrunstubStatus) + From cbffc254752f335999f43692cb356ac284f3bfda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Wed, 17 Jun 2026 12:49:17 +0200 Subject: [PATCH 03/11] =?UTF-8?q?Oppdater=20BestillingProgressDTO=20for=20?= =?UTF-8?q?=C3=A5=20bruke=20LocalDateTime=20og=20forbedre=20DashboardServi?= =?UTF-8?q?ce=20med=20optimalisert=20SQL-sp=C3=B8rring=20og=20JSON-parsing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/dto/BestillingProgressDTO.java | 10 ++++++---- .../no/nav/dolly/service/DashboardService.java | 18 ++++++++++++------ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java index 16ee170f88e..3fb4e7efdf4 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java @@ -8,7 +8,7 @@ import no.nav.dolly.domain.jpa.Testident.Master; import java.io.Serializable; -import java.time.LocalDate; +import java.time.LocalDateTime; import static org.apache.commons.lang3.StringUtils.EMPTY; import static org.apache.commons.lang3.StringUtils.isNotBlank; @@ -20,7 +20,7 @@ @JsonInclude(JsonInclude.Include.NON_NULL) public class BestillingProgressDTO implements Serializable { - private LocalDate sistOppdatert; + private LocalDateTime sistOppdatert; private Long bestillingId; @@ -59,8 +59,9 @@ public class BestillingProgressDTO implements Serializable { @Override public String toString() { - return "{" + - "\"sistOppdatert\":" + sistOppdatert + + + var resultat = "{" + + "\"sistOppdatert\":\"" + sistOppdatert + "\"" + ", \"bestillingId\":" + bestillingId + ", \"ident\":\"" + ident + "\"" + ", \"master\":\"" + master.toString() + "\"" + @@ -95,6 +96,7 @@ public String toString() { getError("udistubStatus" , udistubStatus) + getError("yrkesskadeStatus" , yrkesskadeStatus) + "}"; + return resultat.length() > 120 ? resultat : EMPTY; } private static String getError(String kolonnenavn, String kolonneverdi) { diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 3a18cd60c40..48cfc2ae081 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -217,11 +217,15 @@ public Flux getFeilstatus() { return bestillingProgressRepository.findStatusColumns() .reduce(new StringBuilder(), (StringBuilder sb, String column) -> - sb.append(" or lower(") + sb.append(" or lower(bp.") .append(column) .append(") like '%feil%'")) - .map(query -> new StringBuilder("select * from bestilling_progress where ") + .map(query -> new StringBuilder("select b.sist_oppdatert, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where ") .append(query.substring(4)) + .append(" order by b.sist_oppdatert desc") .toString()) .flatMapMany(query -> entityTemplate.getDatabaseClient() .sql(query) @@ -230,16 +234,18 @@ public Flux getFeilstatus() { .all()) .sort(Comparator.comparing(BestillingProgressDTO::getBestillingId).reversed()) .map(BestillingProgressDTO::toString) - .mapNotNull(this::toJson); + .flatMap(this::toJson); } - private JsonNode toJson(String json) { + private Mono toJson(String json) { try { - return jsonMapper.readTree(json); + return isNotBlank(json) ? + Mono.just(jsonMapper.readTree(json)) : + Mono.empty(); } catch (JacksonException e) { log.error("Feil ved parsing av JSON string: {}", json, e); - return null; + return Mono.empty(); } } } From 0ebfc3c54c67f39408dad1ff870e8c924655cbee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Thu, 18 Jun 2026 13:08:49 +0200 Subject: [PATCH 04/11] =?UTF-8?q?Legg=20til=20DashboardOversiktDTO=20og=20?= =?UTF-8?q?utvid=20DashboardService=20med=20metoder=20for=20=C3=A5=20hente?= =?UTF-8?q?=20detaljert=20og=20summert=20feilstatus,=20samt=20oppdater=20D?= =?UTF-8?q?ashboardController=20for=20nye=20endepunkter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/dto/BestillingProgressDTO.java | 55 +----- .../domain/dto/DashboardOversiktDTO.java | 25 +++ .../domain/projection/OversiktFragment.java | 17 ++ .../dolly/provider/DashboardController.java | 28 ++- .../provider/advice/HttpExceptionAdvice.java | 8 +- .../BestillingProgressRepository.java | 2 +- .../repository/BestillingRepository.java | 17 ++ .../nav/dolly/service/DashboardService.java | 162 +++++++++++++++--- 8 files changed, 227 insertions(+), 87 deletions(-) create mode 100644 apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardOversiktDTO.java create mode 100644 apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/OversiktFragment.java diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java index 3fb4e7efdf4..e0e1bba8aef 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java @@ -8,11 +8,9 @@ import no.nav.dolly.domain.jpa.Testident.Master; import java.io.Serializable; +import java.time.LocalDate; import java.time.LocalDateTime; -import static org.apache.commons.lang3.StringUtils.EMPTY; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - @Data @NoArgsConstructor @AllArgsConstructor @@ -21,6 +19,7 @@ public class BestillingProgressDTO implements Serializable { private LocalDateTime sistOppdatert; + private LocalDate bestillingDato; private Long bestillingId; @@ -28,7 +27,7 @@ public class BestillingProgressDTO implements Serializable { private Master master; private String aaregStatus; - private String arbeidsplassenCVStatus; + private String arbeidsplassencvStatus; private String arbeidssoekerregisteretStatus; private String arenaforvalterStatus; private String brregstubStatus; @@ -56,52 +55,4 @@ public class BestillingProgressDTO implements Serializable { private String sykemeldingStatus; private String udistubStatus; private String yrkesskadeStatus; - - @Override - public String toString() { - - var resultat = "{" + - "\"sistOppdatert\":\"" + sistOppdatert + "\"" + - ", \"bestillingId\":" + bestillingId + - ", \"ident\":\"" + ident + "\"" + - ", \"master\":\"" + master.toString() + "\"" + - getError("aaregStatus" , aaregStatus) + - getError("arbeidsplassenCVStatus" , arbeidsplassenCVStatus) + - getError("arbeidssoekerregisteretStatus" , arbeidssoekerregisteretStatus) + - getError("arenaforvalterStatus" , arenaforvalterStatus) + - getError("brregstubStatus" , brregstubStatus) + - getError("dokarkivStatus" , dokarkivStatus) + - getError("etterlatteStatus" , etterlatteStatus) + - getError("feil" , feil) + - getError("fullmaktStatus" , fullmaktStatus) + - getError("histarkStatus" , histarkStatus) + - getError("inntektsmeldingStatus" , inntektsmeldingStatus) + - getError("inntektstubStatus" , inntektstubStatus) + - getError("instdataStatus" , instdataStatus) + - getError("kelvinAapStatus" , kelvinAapStatus) + - getError("kontoregisterStatus" , kontoregisterStatus) + - getError("krrstubStatus" , krrstubStatus) + - getError("medlStatus" , medlStatus) + - getError("nomStatus" , nomStatus) + - getError("pdlForvalterStatus" , pdlForvalterStatus) + - getError("pdlImportStatus" , pdlImportStatus) + - getError("pdlOrdreStatus" , isNotBlank(pdlOrdreStatus) ? - pdlOrdreStatus.substring(15) : EMPTY) + - getError("pdlPersonStatus" , pdlPersonStatus) + - getError("pensjonforvalterStatus" , pensjonforvalterStatus) + - getError("sigrunstubStatus" , sigrunstubStatus) + - getError("skattekortStatus" , skattekortStatus) + - getError("skjermingsregisterStatus" , skjermingsregisterStatus) + - getError("sykemeldingStatus" , sykemeldingStatus) + - getError("udistubStatus" , udistubStatus) + - getError("yrkesskadeStatus" , yrkesskadeStatus) + - "}"; - return resultat.length() > 120 ? resultat : EMPTY; - } - - private static String getError(String kolonnenavn, String kolonneverdi) { - - return isNotBlank(kolonneverdi) && kolonneverdi.toLowerCase().contains("feil") ? - ", \"%s\":\"%s\"".formatted(kolonnenavn, kolonneverdi) : ""; - } } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardOversiktDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardOversiktDTO.java new file mode 100644 index 00000000000..f400d10507b --- /dev/null +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardOversiktDTO.java @@ -0,0 +1,25 @@ +package no.nav.dolly.domain.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Month; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DashboardOversiktDTO { + + @JsonIgnore + private String aarManed; + + private Integer aar; + private Month maaned; + private Integer totaltAntallPersoner; + private Integer nye; + private Integer gjenopprettede; +} diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/OversiktFragment.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/OversiktFragment.java new file mode 100644 index 00000000000..3da7e3eeb34 --- /dev/null +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/OversiktFragment.java @@ -0,0 +1,17 @@ +package no.nav.dolly.domain.projection; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OversiktFragment { + + private String maaned; + private Long antall; + private String gjenopprettstatus; +} diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java index effd6792c35..b55f0a80f6f 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/DashboardController.java @@ -4,15 +4,19 @@ import lombok.RequiredArgsConstructor; import no.nav.dolly.domain.dto.DashboardDollyTeamsDTO; import no.nav.dolly.domain.dto.DashboardOrganisasjonerDTO; +import no.nav.dolly.domain.dto.DashboardOversiktDTO; import no.nav.dolly.domain.dto.DashboardPersonerDTO; import no.nav.dolly.domain.dto.DashboardTeamsDTO; import no.nav.dolly.service.DashboardService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; import tools.jackson.databind.JsonNode; +import java.time.Month; + @RequestMapping("/api/v1/dashboard") @RestController @RequiredArgsConstructor @@ -27,11 +31,27 @@ public Flux getDashboardPersoner() { return dashboardService.getPersonerStatus(); } - @GetMapping(value = "/feil") - @Operation(description = "Henter feilstatus for personer opprettet.") - public Flux getDashboardFeil() { + @GetMapping(value = "/feil/detaljert") + @Operation(description = "Henter detaljert feilstatus for personer opprettet.") + public Flux getDashboardFeilDetaljert(@RequestParam int year, + @RequestParam Month month, + @RequestParam int day) { + + return dashboardService.getFeilstatusDetaljert(year, month, day); + } + + @GetMapping(value = "/feil/summert") + @Operation(description = "Henter summert feilstatus for personer opprettet.") + public Flux getDashboardFeil(@RequestParam int year, @RequestParam Month month) { + + return dashboardService.getFeilstatusSummert(year, month); + } + + @GetMapping(value = "/oversikt") + @Operation(description = "Henter tilgjengelige perioder.") + public Flux getDashboardFeilOversikt() { - return dashboardService.getFeilstatus(); + return dashboardService.getPerioderOversikt(); } @GetMapping(value = "/teams") diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/advice/HttpExceptionAdvice.java b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/advice/HttpExceptionAdvice.java index 16651b9651a..80d026f73fa 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/provider/advice/HttpExceptionAdvice.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/provider/advice/HttpExceptionAdvice.java @@ -30,8 +30,6 @@ @ControllerAdvice public class HttpExceptionAdvice extends DefaultErrorWebExceptionHandler { - private static final String GATEWAY_ORIGINAL_REQUEST_URL = "org.springframework.web.reactive.HandlerMapping.pathWithinHandlerMapping"; - public HttpExceptionAdvice(ErrorAttributes errorAttributes, WebProperties webProperties, ErrorProperties errorProperties, @@ -47,7 +45,7 @@ private ExceptionInformation informationForException(RuntimeException exception, .error(status.getReasonPhrase()) .status(status.value()) .message(exception.getMessage()) - .path(serverWebExchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL)) + .path(serverWebExchange.getRequest().getURI().getPath()) .timestamp(LocalDateTime.now()) .build(); log.warn("HttpException: {}", exceptionInfo); @@ -86,12 +84,12 @@ ExceptionInformation notFoundRequest(ServerWebExchange serverWebExchange, Runtim @ExceptionHandler(Exception.class) @ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR) ExceptionInformation internalServerError(ServerWebExchange serverWebExchange, Exception exception) { - log.error("Uventet feil ved request til {}", serverWebExchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL), exception); + log.error("Uventet feil ved request til {}", serverWebExchange.getRequest().getURI().getPath(), exception); return ExceptionInformation.builder() .error(HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase()) .status(HttpStatus.INTERNAL_SERVER_ERROR.value()) .message(exception.getMessage()) - .path(serverWebExchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL)) + .path(serverWebExchange.getRequest().getURI().getPath()) .timestamp(LocalDateTime.now()) .build(); } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java index 5fa576ea95d..5558d559dcc 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java @@ -54,7 +54,7 @@ where bp.bestilling_id in (select b.id Mono findByIdAndLock(@Param("id") Long id); @Query(""" - SELECT distinct column_name + SELECT DISTINCT column_name FROM information_schema.columns WHERE table_name = 'bestilling_progress' AND column_name LIKE '%status%' diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java index 617f2fa8cd7..5227c58d19a 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java @@ -4,6 +4,7 @@ import no.nav.dolly.domain.projection.BestillingerFragment; import no.nav.dolly.domain.projection.DollyTeamFragment; import no.nav.dolly.domain.projection.OrganisasjonFragment; +import no.nav.dolly.domain.projection.OversiktFragment; import no.nav.dolly.domain.projection.RsBestillingFragment; import no.nav.dolly.domain.projection.TeamFragment; import org.springframework.data.domain.Pageable; @@ -203,4 +204,20 @@ select count(*) antall, to_char(b.sist_oppdatert, 'YYYY-MM') interval, order by interval desc; """) Flux findBestillingerForDollyTeamsOrderBySistOppdatert(); + + @Query(""" + SELECT TO_CHAR(b.sist_oppdatert, 'YYYY-MM') maaned, + COUNT(*) antall, + CASE + when b.opprettet_fra_id is not null then 'GJENOPPRETTING' + when b.gjenopprettet_fra_ident is not null then 'GJENOPPRETTING' + when b.opprett_fra_gruppe is not null then 'GJENOPPRETTING' + else 'NYBESTILLING' + END gjenopprettStatus + FROM bestilling b + JOIN bestilling_progress bp ON b.id = bp.bestilling_id + GROUP BY maaned, gjenopprettStatus + ORDER BY maaned DESC; + """) + Flux findByAvailIntervals(); } \ No newline at end of file diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 48cfc2ae081..91db5782cbb 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -11,14 +11,15 @@ import no.nav.dolly.domain.dto.BestillingProgressDTO; import no.nav.dolly.domain.dto.DashboardDollyTeamsDTO; import no.nav.dolly.domain.dto.DashboardOrganisasjonerDTO; +import no.nav.dolly.domain.dto.DashboardOversiktDTO; import no.nav.dolly.domain.dto.DashboardPersonerDTO; import no.nav.dolly.domain.dto.DashboardTeamsDTO; -import no.nav.dolly.domain.jpa.BestillingProgress; import no.nav.dolly.domain.jpa.Bruker; import no.nav.dolly.domain.projection.BestillingerFragment; import no.nav.dolly.domain.projection.DollyTeam2Fragment; import no.nav.dolly.domain.projection.DollyTeamFragment; import no.nav.dolly.domain.projection.OrganisasjonFragment; +import no.nav.dolly.domain.projection.OversiktFragment; import no.nav.dolly.domain.projection.TeamFragment; import no.nav.dolly.repository.BestillingProgressRepository; import no.nav.dolly.repository.BestillingRepository; @@ -31,16 +32,17 @@ import reactor.util.function.Tuples; import tools.jackson.core.JacksonException; import tools.jackson.databind.JsonNode; -import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; +import tools.jackson.databind.node.ObjectNode; +import java.time.Month; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.stream.Collectors; @@ -53,6 +55,7 @@ public class DashboardService { private static final String INGEN_TEAM = "Tilhører ikke noe team"; + private static final Set IDENTITETSFELT = Set.of("sistOppdatert", "bestillingId", "ident", "master"); private final Altinn3TilgangServiceConsumer altinn3TilgangServiceConsumer; private final BestillingRepository bestillingRepository; @@ -207,45 +210,154 @@ private static DashboardOrganisasjonerDTO.Entry toOrganisasjonEntry(String orgNu } private static DashboardDollyTeamsDTO.Entry toDollyTeamEntry(DollyTeamFragment fragment, - Map oppslag) { + Map oppslag) { var info = fragment.getInformasjon().split("\\|"); return new DashboardDollyTeamsDTO.Entry(info[0], info[1], toIntExact(oppslag.get(info[2]))); } - public Flux getFeilstatus() { + public Flux getFeilstatusSummert(int year, Month month) { + var filter = "%4d-%02d".formatted(year, month.getValue()); return bestillingProgressRepository.findStatusColumns() .reduce(new StringBuilder(), (StringBuilder sb, String column) -> sb.append(" or lower(bp.") - .append(column) - .append(") like '%feil%'")) - .map(query -> new StringBuilder("select b.sist_oppdatert, bp.* " + - "from bestilling b " + - "join bestilling_progress bp on bp.bestilling_id = b.id " + - "where ") - .append(query.substring(4)) - .append(" order by b.sist_oppdatert desc") - .toString()) + .append(column) + .append(") like '%feil%'")) + .map(kolonner -> "select b.sist_oppdatert::date bestilling_dato, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " + + "and (" + + kolonner.substring(4) + + ") order by bestilling_dato") .flatMapMany(query -> entityTemplate.getDatabaseClient() .sql(query) + .bind("range", filter) .map((row, metadata) -> entityTemplate.getConverter() .read(BestillingProgressDTO.class, row, metadata)) .all()) - .sort(Comparator.comparing(BestillingProgressDTO::getBestillingId).reversed()) - .map(BestillingProgressDTO::toString) - .flatMap(this::toJson); + .groupBy(BestillingProgressDTO::getBestillingDato) + .flatMap(Flux::collectList) + .flatMap(this::tilFeilstatusSummert); + } + + public Flux getFeilstatusDetaljert(int year, Month month, int day) { + + var filter = "%4d-%02d-%02d".formatted(year, month.getValue(), day); + return bestillingProgressRepository.findStatusColumns() + .reduce(new StringBuilder(), (StringBuilder sb, String column) -> + sb.append(" or lower(bp.") + .append(column) + .append(") like '%feil%'")) + .map(kolonner -> "select b.sist_oppdatert, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " + + "and (" + + kolonner.substring(4) + + ") order by b.sist_oppdatert") + .flatMapMany(query -> entityTemplate.getDatabaseClient() + .sql(query) + .bind("range", filter) + .map((row, metadata) -> entityTemplate.getConverter() + .read(BestillingProgressDTO.class, row, metadata)) + .all()) + .sort(Comparator.comparing(BestillingProgressDTO::getSistOppdatert)) + .flatMap(this::tilFeilJson); + } + + private Mono tilFeilstatusSummert(List bestillingProgressDTOS) { + + var resultat = new AtomicReference<>(new HashMap<>()); + bestillingProgressDTOS.forEach(progress -> { + var kilde = (ObjectNode) jsonMapper.valueToTree(progress); + + for (var navn : kilde.propertyNames()) { + var verdi = kilde.get(navn); + if (!IDENTITETSFELT.contains(navn)) { + if ("bestillingDato".equals(navn)) { + resultat.get().putIfAbsent(navn, verdi); + } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { + if (resultat.get().containsKey(navn)) { + var teller = (Integer) resultat.get().get(navn); + resultat.get().put(navn, teller + 1); + } else { + resultat.get().put(navn, 1); + } + } + } + } + }); + return Flux.fromIterable(resultat.get().entrySet()) + .collect(Collectors.toMap(entry -> { + if (((String) entry.getKey()).contains("Status")) { + return ((String) entry.getKey()) + .replace("Status", "Feil"); + } else if (entry.getKey().equals("feil")) { + return "andreFeil"; + } else { + return entry.getKey(); + } + } + , Map.Entry::getValue)) + .flatMap(summert -> summert.isEmpty() ? Mono.empty() : + Mono.just(jsonMapper.valueToTree(summert))); } - private Mono toJson(String json) { + private Mono tilFeilJson(BestillingProgressDTO progress) { - try { - return isNotBlank(json) ? - Mono.just(jsonMapper.readTree(json)) : - Mono.empty(); - } catch (JacksonException e) { - log.error("Feil ved parsing av JSON string: {}", json, e); - return Mono.empty(); + var kilde = (ObjectNode) jsonMapper.valueToTree(progress); + var resultat = jsonMapper.createObjectNode(); + + for (var navn : kilde.propertyNames()) { + var verdi = kilde.get(navn); + if (IDENTITETSFELT.contains(navn)) { + resultat.set(navn, verdi); + } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { + resultat.set(navn, tilJsonEllerTekst(verdi)); + } } + return Mono.just(resultat); + } + + private JsonNode tilJsonEllerTekst(JsonNode verdi) { + + var tekst = verdi.asString().trim(); + if (tekst.startsWith("{") || tekst.startsWith("[")) { + try { + return jsonMapper.readTree(tekst); + } catch (JacksonException e) { + log.debug("Statusfelt er ikke gyldig JSON – beholdes som tekst", e); + } + } + return verdi; + } + + public Flux getPerioderOversikt() { + + return bestillingRepository.findByAvailIntervals() + .groupBy(OversiktFragment::getMaaned) + .flatMap(Flux::collectList) + .map(fragmenter -> DashboardOversiktDTO.builder() + .aarManed(fragmenter.getFirst().getMaaned()) + .aar(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(0, 4))) + .maaned(Month.of(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(5)))) + .totaltAntallPersoner(fragmenter.stream() + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .nye(fragmenter.stream() + .filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .gjenopprettede(fragmenter.stream() + .filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .build()) + .sort(Comparator.comparing(DashboardOversiktDTO::getAarManed).reversed()); } } From 594e95efe3e6c8fa90c42a2438438a8725bedb79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 08:46:28 +0200 Subject: [PATCH 05/11] =?UTF-8?q?Forbedre=20DashboardService=20med=20h?= =?UTF-8?q?=C3=A5ndtering=20av=20feil=20i=20OrdreResponseDTO=20og=20oppdat?= =?UTF-8?q?er=20tilJsonEllerTekst-metoden=20for=20bedre=20JSON-parsing=20#?= =?UTF-8?q?deploy-test-dolly-backend?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nav/dolly/service/DashboardService.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 91db5782cbb..38689f82e5d 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -25,6 +25,7 @@ import no.nav.dolly.repository.BestillingRepository; import no.nav.dolly.repository.BrukerRepository; import no.nav.dolly.repository.TeamRepository; +import no.nav.testnav.libs.dto.pdlforvalter.v1.OrdreResponseDTO; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; @@ -315,23 +316,63 @@ private Mono tilFeilJson(BestillingProgressDTO progress) { if (IDENTITETSFELT.contains(navn)) { resultat.set(navn, verdi); } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { - resultat.set(navn, tilJsonEllerTekst(verdi)); + resultat.set(navn, tilJsonEllerTekst(verdi, navn)); } } return Mono.just(resultat); } - private JsonNode tilJsonEllerTekst(JsonNode verdi) { + private JsonNode tilJsonEllerTekst(JsonNode json, String navn) { - var tekst = verdi.asString().trim(); - if (tekst.startsWith("{") || tekst.startsWith("[")) { + var verdi = json.asString().trim(); + if (verdi.startsWith("{") || verdi.startsWith("[")) { try { - return jsonMapper.readTree(tekst); + if (!"pdlOrdreStatus".equals(navn)) { + return jsonMapper.readTree(verdi); + } else { + var ordreRespons = jsonMapper.readValue(verdi, OrdreResponseDTO.class); + var feilRespons = getFeil(ordreRespons); + var responsTekst = jsonMapper.writeValueAsString(feilRespons); + return jsonMapper.readTree(responsTekst); + } } catch (JacksonException e) { log.debug("Statusfelt er ikke gyldig JSON – beholdes som tekst", e); } } - return verdi; + return json; + } + + private static OrdreResponseDTO getFeil(OrdreResponseDTO response) { + + return OrdreResponseDTO.builder() + .hovedperson(getError(response.getHovedperson())) + .relasjoner(response.getRelasjoner().stream() + .map(DashboardService::getError) + .toList()) + .build(); + } + + private static OrdreResponseDTO.PersonHendelserDTO getError(OrdreResponseDTO.PersonHendelserDTO hendelser) { + + return OrdreResponseDTO.PersonHendelserDTO.builder() + .ident(hendelser.getIdent()) + .ordrer(hendelser.getOrdrer().stream() + .filter(status -> status.getHendelser().stream() + .anyMatch(hendelse -> isNotBlank(hendelse.getError()))) + .map(status -> OrdreResponseDTO.PdlStatusDTO.builder() + .ident(status.getIdent()) + .infoElement(status.getInfoElement()) + .hendelser(status.getHendelser().stream() + .filter(hendelse -> isNotBlank(hendelse.getError())) + .map(hendelse -> OrdreResponseDTO.HendelseDTO.builder() + .id(hendelse.getId()) + .status(hendelse.getStatus()) + .error(hendelse.getError()) + .build()) + .toList()) + .build()) + .toList()) + .build(); } public Flux getPerioderOversikt() { From 62f481d668fa78b38dc50c6470e02e6060c42e4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 09:49:17 +0200 Subject: [PATCH 06/11] =?UTF-8?q?Forbedre=20DashboardService=20med=20optim?= =?UTF-8?q?alisering=20av=20SQL-sp=C3=B8rringer=20og=20forbedret=20h=C3=A5?= =?UTF-8?q?ndtering=20av=20feilstatus,=20samt=20oppdatering=20av=20DTO-er?= =?UTF-8?q?=20for=20bedre=20databehandling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nav/dolly/service/DashboardService.java | 129 +++++++++--------- .../dolly/service/DashboardServiceTest.java | 96 +++++++++++++ 2 files changed, 158 insertions(+), 67 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 38689f82e5d..62b8e2acae4 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -37,13 +37,15 @@ import tools.jackson.databind.node.ObjectNode; import java.time.Month; +import java.time.YearMonth; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; +import java.util.StringJoiner; import java.util.function.Function; import java.util.stream.Collectors; @@ -98,8 +100,7 @@ public Flux getTeamsStatus() { .flatMapMany(teams -> bestillingRepository.findBestillingerForTeamsOrderBySistOppdatert() .groupBy(TeamFragment::getInterval) .flatMap(Flux::collectList) - .flatMap(fragments -> Mono.just( - Tuples.of(groupFragmentsByTeam(fragments, teams), fragments.getFirst().getInterval())))) + .map(fragments -> Tuples.of(groupFragmentsByTeam(fragments, teams), fragments.getFirst().getInterval()))) .map(tuple -> DashboardTeamsDTO.builder() .interval(tuple.getT2()) @@ -125,8 +126,7 @@ public Flux getOrganisasjonerStatus() { .flatMapMany(oppslag -> bestillingRepository.findBestillingerForOrganisasjonerOrderBySistOppdatert() .groupBy(OrganisasjonFragment::getInterval) .flatMap(Flux::collectList) - .flatMap(fragments -> Mono.just( - Tuples.of(groupFragmentsByOrganisasjon(fragments, oppslag.getT2()), fragments.getFirst().getInterval()))) + .map(fragments -> Tuples.of(groupFragmentsByOrganisasjon(fragments, oppslag.getT2()), fragments.getFirst().getInterval())) .map(tuple -> DashboardOrganisasjonerDTO.builder() .interval(tuple.getT2()) .organisasjoner(tuple.getT1().entrySet().stream() @@ -194,7 +194,9 @@ private static Map> groupFragmentsByOrganisasjon(List>(); fragments.forEach(fragment -> { var orgNummer = brukerToOrgnummer.get(fragment.getBrukerid()); - grouped.computeIfAbsent(orgNummer, _ -> new HashSet<>()).add(fragment.getBrukerid()); + if (Objects.nonNull(orgNummer)) { + grouped.computeIfAbsent(orgNummer, _ -> new HashSet<>()).add(fragment.getBrukerid()); + } }); return grouped; } @@ -220,18 +222,12 @@ private static DashboardDollyTeamsDTO.Entry toDollyTeamEntry(DollyTeamFragment f public Flux getFeilstatusSummert(int year, Month month) { var filter = "%4d-%02d".formatted(year, month.getValue()); - return bestillingProgressRepository.findStatusColumns() - .reduce(new StringBuilder(), (StringBuilder sb, String column) -> - sb.append(" or lower(bp.") - .append(column) - .append(") like '%feil%'")) - .map(kolonner -> "select b.sist_oppdatert::date bestilling_dato, bp.* " + - "from bestilling b " + - "join bestilling_progress bp on bp.bestilling_id = b.id " + - "where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " + - "and (" + - kolonner.substring(4) + - ") order by bestilling_dato") + return buildFeilWhereFragment() + .map(feilFilter -> "select b.sist_oppdatert::date bestilling_dato, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " + + "and (" + feilFilter + ") order by bestilling_dato") .flatMapMany(query -> entityTemplate.getDatabaseClient() .sql(query) .bind("range", filter) @@ -246,18 +242,12 @@ public Flux getFeilstatusSummert(int year, Month month) { public Flux getFeilstatusDetaljert(int year, Month month, int day) { var filter = "%4d-%02d-%02d".formatted(year, month.getValue(), day); - return bestillingProgressRepository.findStatusColumns() - .reduce(new StringBuilder(), (StringBuilder sb, String column) -> - sb.append(" or lower(bp.") - .append(column) - .append(") like '%feil%'")) - .map(kolonner -> "select b.sist_oppdatert, bp.* " + - "from bestilling b " + - "join bestilling_progress bp on bp.bestilling_id = b.id " + - "where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " + - "and (" + - kolonner.substring(4) + - ") order by b.sist_oppdatert") + return buildFeilWhereFragment() + .map(feilFilter -> "select b.sist_oppdatert, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " + + "and (" + feilFilter + ") order by b.sist_oppdatert") .flatMapMany(query -> entityTemplate.getDatabaseClient() .sql(query) .bind("range", filter) @@ -268,9 +258,16 @@ public Flux getFeilstatusDetaljert(int year, Month month, int day) { .flatMap(this::tilFeilJson); } + private Mono buildFeilWhereFragment() { + return bestillingProgressRepository.findStatusColumns() + .reduce(new StringJoiner(" or "), (joiner, column) -> + joiner.add("lower(bp." + column + ") like '%feil%'")) + .map(StringJoiner::toString); + } + private Mono tilFeilstatusSummert(List bestillingProgressDTOS) { - var resultat = new AtomicReference<>(new HashMap<>()); + Map resultat = new HashMap<>(); bestillingProgressDTOS.forEach(progress -> { var kilde = (ObjectNode) jsonMapper.valueToTree(progress); @@ -278,32 +275,26 @@ private Mono tilFeilstatusSummert(List bestilli var verdi = kilde.get(navn); if (!IDENTITETSFELT.contains(navn)) { if ("bestillingDato".equals(navn)) { - resultat.get().putIfAbsent(navn, verdi); + resultat.putIfAbsent(navn, verdi); } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { - if (resultat.get().containsKey(navn)) { - var teller = (Integer) resultat.get().get(navn); - resultat.get().put(navn, teller + 1); - } else { - resultat.get().put(navn, 1); - } + resultat.merge(navn, 1, (existing, _) -> (Integer) existing + 1); } } } }); - return Flux.fromIterable(resultat.get().entrySet()) + var summert = resultat.entrySet().stream() .collect(Collectors.toMap(entry -> { - if (((String) entry.getKey()).contains("Status")) { - return ((String) entry.getKey()) - .replace("Status", "Feil"); - } else if (entry.getKey().equals("feil")) { + var key = entry.getKey(); + if (key.contains("Status")) { + return key.replace("Status", "Feil"); + } else if ("feil".equals(key)) { return "andreFeil"; } else { - return entry.getKey(); + return key; } - } - , Map.Entry::getValue)) - .flatMap(summert -> summert.isEmpty() ? Mono.empty() : - Mono.just(jsonMapper.valueToTree(summert))); + }, + Map.Entry::getValue)); + return summert.isEmpty() ? Mono.empty() : Mono.just(jsonMapper.valueToTree(summert)); } private Mono tilFeilJson(BestillingProgressDTO progress) { @@ -380,25 +371,29 @@ public Flux getPerioderOversikt() { return bestillingRepository.findByAvailIntervals() .groupBy(OversiktFragment::getMaaned) .flatMap(Flux::collectList) - .map(fragmenter -> DashboardOversiktDTO.builder() - .aarManed(fragmenter.getFirst().getMaaned()) - .aar(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(0, 4))) - .maaned(Month.of(Integer.parseInt(fragmenter.getFirst().getMaaned().substring(5)))) - .totaltAntallPersoner(fragmenter.stream() - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .nye(fragmenter.stream() - .filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus())) - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .gjenopprettede(fragmenter.stream() - .filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus())) - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .build()) + .map(fragmenter -> { + var aarManed = fragmenter.getFirst().getMaaned(); + var yearMonth = YearMonth.parse(aarManed); + return DashboardOversiktDTO.builder() + .aarManed(aarManed) + .aar(yearMonth.getYear()) + .maaned(yearMonth.getMonth()) + .totaltAntallPersoner(fragmenter.stream() + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .nye(fragmenter.stream() + .filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .gjenopprettede(fragmenter.stream() + .filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .build(); + }) .sort(Comparator.comparing(DashboardOversiktDTO::getAarManed).reversed()); } } diff --git a/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java b/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java index e8d9749ddb6..b717b09790d 100644 --- a/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java +++ b/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java @@ -14,6 +14,7 @@ import no.nav.dolly.domain.projection.DollyTeam2Fragment; import no.nav.dolly.domain.projection.DollyTeamFragment; import no.nav.dolly.domain.projection.OrganisasjonFragment; +import no.nav.dolly.domain.projection.OversiktFragment; import no.nav.dolly.domain.projection.TeamFragment; import no.nav.dolly.repository.BestillingRepository; import no.nav.dolly.repository.BrukerRepository; @@ -27,6 +28,7 @@ import reactor.test.StepVerifier; import java.time.LocalDate; +import java.time.Month; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -425,6 +427,26 @@ void shouldSortOrganisasjonerByNavnAlphabetically() { .verifyComplete(); } + @Test + void shouldSkipOrganisasjonFragmentsWithNoBrukerserviceMatch() { + when(altinn3TilgangServiceConsumer.getOrganisasjoner()).thenReturn(Flux.empty()); + when(brukerServiceConsumer.getAlleBrukere()).thenReturn(Flux.just( + BrukerDTO.builder().id("kjent").organisasjonsnummer("123456789").build() + )); + when(bestillingRepository.findBestillingerForOrganisasjonerOrderBySistOppdatert()) + .thenReturn(Flux.just( + organisasjonFragment(INTERVAL_1, "kjent"), + organisasjonFragment(INTERVAL_1, "ukjent") + )); + + StepVerifier.create(dashboardService.getOrganisasjonerStatus()) + .assertNext(dto -> { + assertThat(dto.getOrganisasjoner()).hasSize(1); + assertThat(dto.getTotaltAntallOrganisasjoner()).isEqualTo(1); + }) + .verifyComplete(); + } + // ── getDollyTeamsStatus ────────────────────────────────────────────────── @Test @@ -528,6 +550,72 @@ void shouldSortDollyTeamsStatusByIntervalDescending() { assertThat(results.get(1).getInterval()).isEqualTo(INTERVAL_1); } + // ── getPerioderOversikt ────────────────────────────────────────────────── + + @Test + void shouldReturnEmptyPerioderWhenNoIntervals() { + when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.empty()); + + StepVerifier.create(dashboardService.getPerioderOversikt()) + .verifyComplete(); + } + + @Test + void shouldParseAarManedIntoAarAndMaaned() { + when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just( + oversiktFragment("2024-03", 10L, "NYBESTILLING") + )); + + StepVerifier.create(dashboardService.getPerioderOversikt()) + .assertNext(dto -> { + assertThat(dto.getAarManed()).isEqualTo("2024-03"); + assertThat(dto.getAar()).isEqualTo(2024); + assertThat(dto.getMaaned()).isEqualTo(Month.MARCH); + }) + .verifyComplete(); + } + + @Test + void shouldSumTotaltAntallPersonerForSamePeriod() { + when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just( + oversiktFragment("2024-01", 5L, "NYBESTILLING"), + oversiktFragment("2024-01", 3L, "GJENOPPRETTING") + )); + + StepVerifier.create(dashboardService.getPerioderOversikt()) + .assertNext(dto -> assertThat(dto.getTotaltAntallPersoner()).isEqualTo(8)) + .verifyComplete(); + } + + @Test + void shouldSumNyeAndGjenopprettedeByGjenopprettstatus() { + when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just( + oversiktFragment("2024-01", 4L, "NYBESTILLING"), + oversiktFragment("2024-01", 2L, "GJENOPPRETTING"), + oversiktFragment("2024-01", 1L, "UKJENT") + )); + + StepVerifier.create(dashboardService.getPerioderOversikt()) + .assertNext(dto -> { + assertThat(dto.getNye()).isEqualTo(4); + assertThat(dto.getGjenopprettede()).isEqualTo(2); + }) + .verifyComplete(); + } + + @Test + void shouldSortPerioderOversiktByAarManedDescending() { + when(bestillingRepository.findByAvailIntervals()).thenReturn(Flux.just( + oversiktFragment("2024-01", 1L, "NYBESTILLING"), + oversiktFragment("2024-02", 1L, "NYBESTILLING") + )); + + StepVerifier.create(dashboardService.getPerioderOversikt()) + .assertNext(dto -> assertThat(dto.getAarManed()).isEqualTo("2024-02")) + .assertNext(dto -> assertThat(dto.getAarManed()).isEqualTo("2024-01")) + .verifyComplete(); + } + // ── helpers ────────────────────────────────────────────────────────────── private static BestillingerFragment fragment(LocalDate dato, Long personer, @@ -563,4 +651,12 @@ private static DollyTeamFragment dollyTeamFragment(String interval, String navn, .informasjon(navn + "|" + beskrivelse + "|" + brukerid) .build(); } + + private static OversiktFragment oversiktFragment(String maaned, Long antall, String gjenopprettstatus) { + return OversiktFragment.builder() + .maaned(maaned) + .antall(antall) + .gjenopprettstatus(gjenopprettstatus) + .build(); + } } From b5d7a70013d9c551538c1ff91a15d719a437014d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 10:07:43 +0200 Subject: [PATCH 07/11] =?UTF-8?q?Legg=20til=20metoder=20for=20=C3=A5=20hen?= =?UTF-8?q?te=20summert=20og=20detaljert=20feilstatus=20i=20DashboardServi?= =?UTF-8?q?ce,=20samt=20oppdater=20DashboardOversiktDTO=20for=20oversiktsd?= =?UTF-8?q?ata?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../nav/dolly/service/DashboardService.java | 140 +++++++++--------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 62b8e2acae4..792db983deb 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -166,6 +166,76 @@ public Flux getDollyTeamsStatus() { .sort(Comparator.comparing(DashboardDollyTeamsDTO::getInterval).reversed()); } + public Flux getFeilstatusSummert(int year, Month month) { + + var filter = "%4d-%02d".formatted(year, month.getValue()); + return buildFeilWhereFragment() + .map(feilFilter -> "select b.sist_oppdatert::date bestilling_dato, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " + + "and (" + feilFilter + ") order by bestilling_dato") + .flatMapMany(query -> entityTemplate.getDatabaseClient() + .sql(query) + .bind("range", filter) + .map((row, metadata) -> entityTemplate.getConverter() + .read(BestillingProgressDTO.class, row, metadata)) + .all()) + .groupBy(BestillingProgressDTO::getBestillingDato) + .flatMap(Flux::collectList) + .flatMap(this::tilFeilstatusSummert); + } + + public Flux getFeilstatusDetaljert(int year, Month month, int day) { + + var filter = "%4d-%02d-%02d".formatted(year, month.getValue(), day); + return buildFeilWhereFragment() + .map(feilFilter -> "select b.sist_oppdatert, bp.* " + + "from bestilling b " + + "join bestilling_progress bp on bp.bestilling_id = b.id " + + "where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " + + "and (" + feilFilter + ") order by b.sist_oppdatert") + .flatMapMany(query -> entityTemplate.getDatabaseClient() + .sql(query) + .bind("range", filter) + .map((row, metadata) -> entityTemplate.getConverter() + .read(BestillingProgressDTO.class, row, metadata)) + .all()) + .sort(Comparator.comparing(BestillingProgressDTO::getSistOppdatert)) + .flatMap(this::tilFeilJson); + } + + public Flux getPerioderOversikt() { + + return bestillingRepository.findByAvailIntervals() + .groupBy(OversiktFragment::getMaaned) + .flatMap(Flux::collectList) + .map(fragmenter -> { + var aarManed = fragmenter.getFirst().getMaaned(); + var yearMonth = YearMonth.parse(aarManed); + return DashboardOversiktDTO.builder() + .aarManed(aarManed) + .aar(yearMonth.getYear()) + .maaned(yearMonth.getMonth()) + .totaltAntallPersoner(fragmenter.stream() + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .nye(fragmenter.stream() + .filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .gjenopprettede(fragmenter.stream() + .filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus())) + .map(OversiktFragment::getAntall) + .mapToInt(Long::intValue) + .sum()) + .build(); + }) + .sort(Comparator.comparing(DashboardOversiktDTO::getAarManed).reversed()); + } + private static long sumByStatus(List fragments, Function statusGetter, String value) { @@ -219,45 +289,6 @@ private static DashboardDollyTeamsDTO.Entry toDollyTeamEntry(DollyTeamFragment f return new DashboardDollyTeamsDTO.Entry(info[0], info[1], toIntExact(oppslag.get(info[2]))); } - public Flux getFeilstatusSummert(int year, Month month) { - - var filter = "%4d-%02d".formatted(year, month.getValue()); - return buildFeilWhereFragment() - .map(feilFilter -> "select b.sist_oppdatert::date bestilling_dato, bp.* " + - "from bestilling b " + - "join bestilling_progress bp on bp.bestilling_id = b.id " + - "where to_char(b.sist_oppdatert, 'YYYY-MM') = :range " + - "and (" + feilFilter + ") order by bestilling_dato") - .flatMapMany(query -> entityTemplate.getDatabaseClient() - .sql(query) - .bind("range", filter) - .map((row, metadata) -> entityTemplate.getConverter() - .read(BestillingProgressDTO.class, row, metadata)) - .all()) - .groupBy(BestillingProgressDTO::getBestillingDato) - .flatMap(Flux::collectList) - .flatMap(this::tilFeilstatusSummert); - } - - public Flux getFeilstatusDetaljert(int year, Month month, int day) { - - var filter = "%4d-%02d-%02d".formatted(year, month.getValue(), day); - return buildFeilWhereFragment() - .map(feilFilter -> "select b.sist_oppdatert, bp.* " + - "from bestilling b " + - "join bestilling_progress bp on bp.bestilling_id = b.id " + - "where to_char(b.sist_oppdatert, 'YYYY-MM-DD') = :range " + - "and (" + feilFilter + ") order by b.sist_oppdatert") - .flatMapMany(query -> entityTemplate.getDatabaseClient() - .sql(query) - .bind("range", filter) - .map((row, metadata) -> entityTemplate.getConverter() - .read(BestillingProgressDTO.class, row, metadata)) - .all()) - .sort(Comparator.comparing(BestillingProgressDTO::getSistOppdatert)) - .flatMap(this::tilFeilJson); - } - private Mono buildFeilWhereFragment() { return bestillingProgressRepository.findStatusColumns() .reduce(new StringJoiner(" or "), (joiner, column) -> @@ -365,35 +396,4 @@ private static OrdreResponseDTO.PersonHendelserDTO getError(OrdreResponseDTO.Per .toList()) .build(); } - - public Flux getPerioderOversikt() { - - return bestillingRepository.findByAvailIntervals() - .groupBy(OversiktFragment::getMaaned) - .flatMap(Flux::collectList) - .map(fragmenter -> { - var aarManed = fragmenter.getFirst().getMaaned(); - var yearMonth = YearMonth.parse(aarManed); - return DashboardOversiktDTO.builder() - .aarManed(aarManed) - .aar(yearMonth.getYear()) - .maaned(yearMonth.getMonth()) - .totaltAntallPersoner(fragmenter.stream() - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .nye(fragmenter.stream() - .filter(fragment -> "NYBESTILLING".equals(fragment.getGjenopprettstatus())) - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .gjenopprettede(fragmenter.stream() - .filter(fragment -> "GJENOPPRETTING".equals(fragment.getGjenopprettstatus())) - .map(OversiktFragment::getAntall) - .mapToInt(Long::intValue) - .sum()) - .build(); - }) - .sort(Comparator.comparing(DashboardOversiktDTO::getAarManed).reversed()); - } } From 3c665f63456ad11add149cf5315372296aa363e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 11:08:07 +0200 Subject: [PATCH 08/11] Refactor Dashboard components to remove unused status fields and simplify data handling #deploy-test-dolly-backend --- .../domain/dto/DashboardPersonerDTO.java | 2 - .../projection/BestillingerFragment.java | 2 - .../repository/BestillingRepository.java | 23 ++------- .../nav/dolly/service/DashboardService.java | 10 ++-- .../dolly/service/DashboardServiceTest.java | 47 ++++--------------- 5 files changed, 18 insertions(+), 66 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardPersonerDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardPersonerDTO.java index 67696058884..150654b47f4 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardPersonerDTO.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/DashboardPersonerDTO.java @@ -17,6 +17,4 @@ public class DashboardPersonerDTO { private Long personerTotalt; private Long nye; private Long gjenopprettede; - private Long pdlFeil; - private Long andreFeil; } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/BestillingerFragment.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/BestillingerFragment.java index d65efb6f674..96552648d0c 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/BestillingerFragment.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/projection/BestillingerFragment.java @@ -16,6 +16,4 @@ public class BestillingerFragment { private LocalDate dato; private Long personer; private String gjenopprettstatus; - private String pdlstatus; - private String annenstatus; } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java index 5227c58d19a..77786d79cf2 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java @@ -147,31 +147,16 @@ select count(*) personer, else 'NYBESTILLING' end as gjenopprettStatus, case - when lower(bp.pdl_person_status) not like 'synkronisering%' then 'FEIL' - when lower(bp.pdl_forvalter_status) like '%feil%' then 'FEIL' - when lower(bp.pdl_ordre_status) like '%feil%' then 'FEIL' - else 'OK' - end as pdlStatus, - case - when lower(bp.pensjonforvalter_status) like '%feil%' then 'FEIL' - when lower(bp.aareg_status) like '%feil%' then 'FEIL' - when lower(bp.arenaforvalter_status) like '%feil%' then 'FEIL' - when lower(bp.instdata_status) like '%feil%' then 'FEIL' - when lower(bp.inntektsstub_status) like '%feil%' then 'FEIL' - when lower(bp.inntektsmelding_status) like '%feil%' then 'FEIL' - when lower(bp.sigrunstub_status) like '%feil%' then 'FEIL' - when lower(bp.dokarkiv_status) like '%feil%' then 'FEIL' - when lower(bp.feil) like '%feil%' then 'FEIL' - else 'OK' - end as annenStatus + when b.pdl_import is not null then true + else false + end as testnorgeIdent from bestilling b join bestilling_progress bp on b.id = bp.bestilling_id - group by dato, gjenopprettStatus, pdlStatus, annenStatus + group by dato, gjenopprettStatus, testnorgeIdent order by dato desc """) Flux findBestillingerOrderBySistOppdatert(); - @Query(""" select count(*) antall, to_char(b.sist_oppdatert, 'YYYY-MM') interval, br.epost from bestilling b diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 792db983deb..550d6a62f10 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -82,8 +82,6 @@ public Flux getPersonerStatus() { .mapToLong(BestillingerFragment::getPersoner).sum()) .nye(sumByStatus(fragmentliste, BestillingerFragment::getGjenopprettstatus, "NYBESTILLING")) .gjenopprettede(sumByStatus(fragmentliste, BestillingerFragment::getGjenopprettstatus, "GJENOPPRETTING")) - .pdlFeil(sumByStatus(fragmentliste, BestillingerFragment::getPdlstatus, "FEIL")) - .andreFeil(sumByStatus(fragmentliste, BestillingerFragment::getAnnenstatus, "FEIL")) .build()) .sort(Comparator.comparing(DashboardPersonerDTO::getDato).reversed()); } @@ -182,8 +180,8 @@ public Flux getFeilstatusSummert(int year, Month month) { .read(BestillingProgressDTO.class, row, metadata)) .all()) .groupBy(BestillingProgressDTO::getBestillingDato) - .flatMap(Flux::collectList) - .flatMap(this::tilFeilstatusSummert); + .concatMap(Flux::collectList) + .concatMap(this::tilFeilstatusSummert); } public Flux getFeilstatusDetaljert(int year, Month month, int day) { @@ -237,8 +235,8 @@ public Flux getPerioderOversikt() { } private static long sumByStatus(List fragments, - Function statusGetter, - String value) { + Function statusGetter, + Object value) { return fragments.stream() .filter(f -> value.equals(statusGetter.apply(f))) diff --git a/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java b/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java index b717b09790d..4fed55d939b 100644 --- a/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java +++ b/apps/dolly-backend/src/test/java/no/nav/dolly/service/DashboardServiceTest.java @@ -76,8 +76,8 @@ void shouldReturnEmptyWhenNoFragments() { @Test void shouldSumPersonerTotaltForSingleDate() { - var f1 = fragment(DATE_1, 5L, "NYBESTILLING", "OK", "OK"); - var f2 = fragment(DATE_1, 3L, "NYBESTILLING", "OK", "OK"); + var f1 = fragment(DATE_1, 5L, "NYBESTILLING"); + var f2 = fragment(DATE_1, 3L, "NYBESTILLING"); when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(f1, f2)); StepVerifier.create(dashboardService.getPersonerStatus()) @@ -90,8 +90,8 @@ void shouldSumPersonerTotaltForSingleDate() { @Test void shouldCountNyeByGjenopprettstatusNYBESTILLING() { - var nybestilling = fragment(DATE_1, 4L, "NYBESTILLING", "OK", "OK"); - var gjenoppretting = fragment(DATE_1, 2L, "GJENOPPRETTING", "OK", "OK"); + var nybestilling = fragment(DATE_1, 4L, "NYBESTILLING"); + var gjenoppretting = fragment(DATE_1, 2L, "GJENOPPRETTING"); when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(nybestilling, gjenoppretting)); StepVerifier.create(dashboardService.getPersonerStatus()) @@ -102,31 +102,10 @@ void shouldCountNyeByGjenopprettstatusNYBESTILLING() { .verifyComplete(); } - @Test - void shouldCountPdlFeilByPdlstatusFEIL() { - var feil = fragment(DATE_1, 3L, "NYBESTILLING", "FEIL", "OK"); - var ok = fragment(DATE_1, 2L, "NYBESTILLING", "OK", "OK"); - when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(feil, ok)); - - StepVerifier.create(dashboardService.getPersonerStatus()) - .assertNext(dto -> assertThat(dto.getPdlFeil()).isEqualTo(3L)) - .verifyComplete(); - } - - @Test - void shouldCountAndreFeilByAnnenstatusFEIL() { - var feil = fragment(DATE_1, 7L, "NYBESTILLING", "OK", "FEIL"); - when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(feil)); - - StepVerifier.create(dashboardService.getPersonerStatus()) - .assertNext(dto -> assertThat(dto.getAndreFeil()).isEqualTo(7L)) - .verifyComplete(); - } - @Test void shouldGroupFragmentsByDateIntoSeparateDtos() { - var d1 = fragment(DATE_1, 10L, "NYBESTILLING", "OK", "OK"); - var d2 = fragment(DATE_2, 5L, "NYBESTILLING", "OK", "OK"); + var d1 = fragment(DATE_1, 10L, "NYBESTILLING"); + var d2 = fragment(DATE_2, 5L, "NYBESTILLING"); when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(d1, d2)); StepVerifier.create(dashboardService.getPersonerStatus()) @@ -137,8 +116,8 @@ void shouldGroupFragmentsByDateIntoSeparateDtos() { @Test void shouldSortPersonerStatusByDateDescending() { - var d1 = fragment(DATE_1, 1L, "NYBESTILLING", "OK", "OK"); - var d2 = fragment(DATE_2, 1L, "NYBESTILLING", "OK", "OK"); + var d1 = fragment(DATE_1, 1L, "NYBESTILLING"); + var d2 = fragment(DATE_2, 1L, "NYBESTILLING"); when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(d1, d2)); StepVerifier.create(dashboardService.getPersonerStatus()) @@ -149,15 +128,13 @@ void shouldSortPersonerStatusByDateDescending() { @Test void shouldProduceZeroCountsWhenNoMatchingStatuses() { - var f = fragment(DATE_1, 5L, "UKJENT", "UKJENT", "UKJENT"); + var f = fragment(DATE_1, 5L, "UKJENT"); when(bestillingRepository.findBestillingerOrderBySistOppdatert()).thenReturn(Flux.just(f)); StepVerifier.create(dashboardService.getPersonerStatus()) .assertNext(dto -> { assertThat(dto.getNye()).isZero(); assertThat(dto.getGjenopprettede()).isZero(); - assertThat(dto.getPdlFeil()).isZero(); - assertThat(dto.getAndreFeil()).isZero(); }) .verifyComplete(); } @@ -619,15 +596,11 @@ void shouldSortPerioderOversiktByAarManedDescending() { // ── helpers ────────────────────────────────────────────────────────────── private static BestillingerFragment fragment(LocalDate dato, Long personer, - String gjenopprettstatus, - String pdlstatus, - String annenstatus) { + String gjenopprettstatus) { return BestillingerFragment.builder() .dato(dato) .personer(personer) .gjenopprettstatus(gjenopprettstatus) - .pdlstatus(pdlstatus) - .annenstatus(annenstatus) .build(); } From 7926289f0331b66176b7a56da3bfc68b53fb834c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 13:13:00 +0200 Subject: [PATCH 09/11] Oppdatering etter review fra Copilot #deploy-test-dolly-backend #deploy-dolly-backend --- .../repository/BestillingProgressRepository.java | 16 ++++++++-------- .../no/nav/dolly/service/DashboardService.java | 14 +++++++------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java index 5558d559dcc..ecbe6a7de08 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingProgressRepository.java @@ -54,13 +54,13 @@ where bp.bestilling_id in (select b.id Mono findByIdAndLock(@Param("id") Long id); @Query(""" - SELECT DISTINCT column_name - FROM information_schema.columns - WHERE table_name = 'bestilling_progress' - AND column_name LIKE '%status%' - AND column_name NOT LIKE 'tps_messaging_status' - OR column_name = 'feil' - ORDER BY column_name; - """) + SELECT DISTINCT column_name + FROM information_schema.columns + WHERE table_name = 'bestilling_progress' + AND column_name NOT LIKE 'tps_messaging_status' + AND (column_name LIKE '%status%' + OR column_name = 'feil') + ORDER BY column_name; + """) Flux findStatusColumns(); } diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java index 550d6a62f10..aef18ff6462 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/service/DashboardService.java @@ -199,7 +199,6 @@ public Flux getFeilstatusDetaljert(int year, Month month, int day) { .map((row, metadata) -> entityTemplate.getConverter() .read(BestillingProgressDTO.class, row, metadata)) .all()) - .sort(Comparator.comparing(BestillingProgressDTO::getSistOppdatert)) .flatMap(this::tilFeilJson); } @@ -291,7 +290,8 @@ private Mono buildFeilWhereFragment() { return bestillingProgressRepository.findStatusColumns() .reduce(new StringJoiner(" or "), (joiner, column) -> joiner.add("lower(bp." + column + ") like '%feil%'")) - .map(StringJoiner::toString); + .map(StringJoiner::toString) + .map(s -> s.isBlank() ? "false" : s); } private Mono tilFeilstatusSummert(List bestillingProgressDTOS) { @@ -300,16 +300,16 @@ private Mono tilFeilstatusSummert(List bestilli bestillingProgressDTOS.forEach(progress -> { var kilde = (ObjectNode) jsonMapper.valueToTree(progress); - for (var navn : kilde.propertyNames()) { + kilde.propertyNames().forEach(navn -> { var verdi = kilde.get(navn); if (!IDENTITETSFELT.contains(navn)) { if ("bestillingDato".equals(navn)) { resultat.putIfAbsent(navn, verdi); } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { - resultat.merge(navn, 1, (existing, _) -> (Integer) existing + 1); + resultat.merge(navn, 1, (existing, one) -> (Integer) existing + (Integer) one); } } - } + }); }); var summert = resultat.entrySet().stream() .collect(Collectors.toMap(entry -> { @@ -331,14 +331,14 @@ private Mono tilFeilJson(BestillingProgressDTO progress) { var kilde = (ObjectNode) jsonMapper.valueToTree(progress); var resultat = jsonMapper.createObjectNode(); - for (var navn : kilde.propertyNames()) { + kilde.propertyNames().forEach(navn -> { var verdi = kilde.get(navn); if (IDENTITETSFELT.contains(navn)) { resultat.set(navn, verdi); } else if (verdi.isString() && verdi.asString().toLowerCase().contains("feil")) { resultat.set(navn, tilJsonEllerTekst(verdi, navn)); } - } + }); return Mono.just(resultat); } From 883cbeee5d1eb02096b12caa7a13fb5d495a9ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 13:27:34 +0200 Subject: [PATCH 10/11] Refactor BestillingRepository to simplify query by removing unused testnorgeIdent field --- .../no/nav/dolly/repository/BestillingRepository.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java index 77786d79cf2..da91fe49c8b 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/repository/BestillingRepository.java @@ -145,14 +145,10 @@ select count(*) personer, when b.gjenopprettet_fra_ident is not null then 'GJENOPPRETTING' when b.opprett_fra_gruppe is not null then 'GJENOPPRETTING' else 'NYBESTILLING' - end as gjenopprettStatus, - case - when b.pdl_import is not null then true - else false - end as testnorgeIdent + end as gjenopprettStatus from bestilling b join bestilling_progress bp on b.id = bp.bestilling_id - group by dato, gjenopprettStatus, testnorgeIdent + group by dato, gjenopprettStatus order by dato desc """) Flux findBestillingerOrderBySistOppdatert(); From a1edb2c5325fd3804ec5b31609c2132abe3e7553 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristen=20H=C3=A6rum?= Date: Fri, 19 Jun 2026 13:42:16 +0200 Subject: [PATCH 11/11] Fix formatting issue in BestillingProgressDTO by correcting whitespace in inntektsstubStatus field #deploy-test-dolly-backend #deploy-dolly-backend --- .../java/no/nav/dolly/domain/dto/BestillingProgressDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java index e0e1bba8aef..e44ff12c3ca 100644 --- a/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java +++ b/apps/dolly-backend/src/main/java/no/nav/dolly/domain/dto/BestillingProgressDTO.java @@ -37,7 +37,7 @@ public class BestillingProgressDTO implements Serializable { private String fullmaktStatus; private String histarkStatus; private String inntektsmeldingStatus; - private String inntektstubStatus; + private String inntektsstubStatus; private String instdataStatus; private String kelvinAapStatus; private String kontoregisterStatus;