diff --git a/_mocks/featureToggles.ts b/_mocks/featureToggles.ts index 4354aab249..1116312867 100644 --- a/_mocks/featureToggles.ts +++ b/_mocks/featureToggles.ts @@ -93,4 +93,8 @@ export const featureTogglesFactory = () => [ key: 'BRUK_INNTEKTSGRADERING_I_UTTAK', value: process.env.VITE_BRUK_INNTEKTSGRADERING_I_UTTAK, }, + { + key: 'AKSJONSPUNKT_OVERLAPPENDE_SAKER', + value: process.env.VITE_AKSJONSPUNKT_OVERLAPPENDE_SAKER, + }, ]; diff --git a/deploy/dev-fss-k9saksbehandling.yml b/deploy/dev-fss-k9saksbehandling.yml index efd0d04955..941c9c9122 100644 --- a/deploy/dev-fss-k9saksbehandling.yml +++ b/deploy/dev-fss-k9saksbehandling.yml @@ -115,3 +115,5 @@ spec: value: "true" - name: BRUK_INNTEKTSGRADERING_I_UTTAK value: "true" + - name: AKSJONSPUNKT_OVERLAPPENDE_SAKER + value: "true" diff --git a/deploy/prod-fss-k9saksbehandling.yml b/deploy/prod-fss-k9saksbehandling.yml index 5dbfb6f5b7..33980a91c8 100644 --- a/deploy/prod-fss-k9saksbehandling.yml +++ b/deploy/prod-fss-k9saksbehandling.yml @@ -1,16 +1,16 @@ -apiVersion: "nais.io/v1alpha1" -kind: "Application" +apiVersion: 'nais.io/v1alpha1' +kind: 'Application' metadata: name: k9-sak-web namespace: k9saksbehandling labels: team: k9saksbehandling annotations: - nais.io/run-as-user: "101" # Fix permissions for nginx - nais.io/read-only-file-system: "false" # Fix permissions for nginx - nginx.ingress.kubernetes.io/proxy-buffer-size: "32k" - nginx.ingress.kubernetes.io/proxy-read-timeout: "600" - nginx.ingress.kubernetes.io/proxy-send-timeout: "600" + nais.io/run-as-user: '101' # Fix permissions for nginx + nais.io/read-only-file-system: 'false' # Fix permissions for nginx + nginx.ingress.kubernetes.io/proxy-buffer-size: '32k' + nginx.ingress.kubernetes.io/proxy-read-timeout: '600' + nginx.ingress.kubernetes.io/proxy-send-timeout: '600' spec: image: {{ image }} port: 9000 @@ -36,72 +36,72 @@ spec: cpu: 200m memory: 32Mi ingresses: - - "https://k9.intern.nav.no/" + - 'https://k9.intern.nav.no/' env: - name: APP_NAME value: k9-sak-web - name: APP_URL - value: "http://k9-sak" + value: 'http://k9-sak' - name: APP_PORT - value: "9000" + value: '9000' - name: APP_URL_K9FORMIDLING - value: "http://k9-formidling" + value: 'http://k9-formidling' - name: APP_URL_K9FORMIDLING_DD - value: "http://k9-formidling-dokumentdata.k9saksbehandling" + value: 'http://k9-formidling-dokumentdata.k9saksbehandling' - name: APP_URL_K9OPPDRAG - value: "http://k9-oppdrag.k9saksbehandling" + value: 'http://k9-oppdrag.k9saksbehandling' - name: APP_URL_KLAGE - value: "http://k9-klage.k9saksbehandling" + value: 'http://k9-klage.k9saksbehandling' - name: APP_URL_K9TILBAKE - value: "http://k9-tilbake.k9saksbehandling" + value: 'http://k9-tilbake.k9saksbehandling' - name: APP_URL_K9FORDEL - value: "http://k9-fordel.k9saksbehandling" + value: 'http://k9-fordel.k9saksbehandling' - name: ENDRINGSLOGG_URL - value: "https://familie-endringslogg.intern.nav.no/" + value: 'https://familie-endringslogg.intern.nav.no/' # Feature-flag - name: KLAGE_KABAL - value: "true" + value: 'true' - name: VARSELTEKST - value: "true" + value: 'true' - name: DOKUMENTDATA - value: "false" + value: 'false' - name: UNNTAKSBEHANDLING - value: "false" + value: 'false' - name: KLAGEBEHANDLING - value: "true" + value: 'true' - name: TILBAKE - value: "true" + value: 'true' - name: SOKNADPERIODESTRIPE - value: "false" + value: 'false' - name: TYPE_MEDISINSKE_OPPLYSNINGER_BREV - value: "true" + value: 'true' - name: LOS_MARKER_BEHANDLING - value: "true" + value: 'true' - name: LOS_MARKER_BEHANDLING_SUBMIT - value: "false" + value: 'false' - name: AKSJONSPUNKT_9015 - value: "true" + value: 'true' - name: ALDERSVILKAR_KRONISK_SYK - value: "true" + value: 'true' - name: FRITEKST_REDIGERING - value: "true" + value: 'true' - name: INKLUDER_KALENDER_PILS - value: "false" + value: 'false' - name: SKJUL_AVSLUTTET_ARBEIDSGIVER - value: "false" + value: 'false' - name: OMS_PUNSJSTRIPE - value: "true" + value: 'true' - name: OMSORGEN_FOR_PERIODISERT - value: "true" + value: 'true' - name: OVERSTYR_BEREGNING - value: "false" + value: 'false' - name: BRUK_V2_MELDINGER - value: "true" + value: 'true' - name: NYE_NOKKELTALL value: "false" - name: UTVIDET_VARSELFELT - value: "true" + value: 'true' - name: SKILL_UT_PRIVATPERSON value: "true" - name: AUTOMATISK_VURDERT_MEDLEMSKAP @@ -112,3 +112,5 @@ spec: value: "true" - name: BRUK_INNTEKTSGRADERING_I_UTTAK value: "true" + - name: AKSJONSPUNKT_OVERLAPPENDE_SAKER + value: 'false' diff --git a/envDir/.env.development b/envDir/.env.development index 895f669c65..3fe056afb0 100644 --- a/envDir/.env.development +++ b/envDir/.env.development @@ -21,3 +21,4 @@ VITE_AUTOMATISK_VURDERT_MEDLEMSKAP=true VITE_BRUK_V2_SAK_DOKUMENTER=true VITE_OPPTJENING_READ_ONLY_PERIODER=true VITE_BRUK_INNTEKTSGRADERING_I_UTTAK=true +VITE_AKSJONSPUNKT_OVERLAPPENDE_SAKER=true diff --git a/feature-toggles.json b/feature-toggles.json index e78ba0c5af..88df02b93c 100644 --- a/feature-toggles.json +++ b/feature-toggles.json @@ -98,5 +98,9 @@ { "key": "BRUK_INNTEKTSGRADERING_I_UTTAK", "value": "${BRUK_INNTEKTSGRADERING_I_UTTAK}" + }, + { + "key": "AKSJONSPUNKT_OVERLAPPENDE_SAKER", + "value": "${AKSJONSPUNKT_OVERLAPPENDE_SAKER}" } ] diff --git a/package.json b/package.json index 517d25d09c..7a6227b0c0 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,8 @@ "css:modules:typegen:watch": "tcm ./packages/v2/gui --camelCase dashes --allowArbitraryExtensions --watch", "css:modules:typegen:check": "tcm ./packages/v2/gui --camelCase dashes --allowArbitraryExtensions --listDifferent", "dev": "yarn cross-env APP_URL_K9FORMIDLING=http://localhost:8060/rest/dummy vite serve", - "dev-lokal": "cross-env NODE_ENV=development APP_URL_SAK=http://k9-sak:8080 APP_URL_K9OPPDRAG=http://k9-oppdrag:8070 APP_URL_K9FORMIDLING_DD=http://k9-formidling-dokumentdata:8294 APP_URL_K9FORMIDLING=http://localhost:8290 SPLITTET_SAMMENLINGNING_BEREGNING=true vite serve", - "dev-med-formidling": "yarn cross-env APP_URL_K9FORMIDLING=http://k9-formidling:8290 SPLITTET_SAMMENLINGNING_BEREGNING=true vite serve", + "dev-lokal": "cross-env NODE_ENV=development APP_URL_SAK=http://k9-sak:8080 APP_URL_K9OPPDRAG=http://k9-oppdrag:8070 APP_URL_K9FORMIDLING_DD=http://k9-formidling-dokumentdata:8294 APP_URL_K9FORMIDLING=http://localhost:8290 SPLITTET_SAMMENLINGNING_BEREGNING=true AKSJONSPUNKT_OVERLAPPENDE_SAKER=true vite serve", + "dev-med-formidling": "yarn cross-env APP_URL_K9FORMIDLING=http://k9-formidling:8290 SPLITTET_SAMMENLINGNING_BEREGNING=true AKSJONSPUNKT_OVERLAPPENDE_SAKER=true vite serve", "build": "VITE_SENTRY_RELEASE=$(git rev-parse --short HEAD) vite build", "up": "docker-compose up -d --remove-orphans --build", "down": "docker-compose down", diff --git a/packages/behandling-pleiepenger/src/components/Uttak.tsx b/packages/behandling-pleiepenger/src/components/Uttak.tsx index 7bba09b114..011c79e66f 100644 --- a/packages/behandling-pleiepenger/src/components/Uttak.tsx +++ b/packages/behandling-pleiepenger/src/components/Uttak.tsx @@ -1,3 +1,4 @@ +import { useContext } from 'react'; import aksjonspunktCodes from '@fpsak-frontend/kodeverk/src/aksjonspunktCodes'; import aksjonspunktStatus from '@fpsak-frontend/kodeverk/src/aksjonspunktStatus'; import { findEndpointsFromRels, httpErrorHandler } from '@fpsak-frontend/utils'; @@ -5,7 +6,11 @@ import { VilkarResultPicker } from '@k9-sak-web/prosess-felles'; import { Inntektsgradering, Uttak } from '@k9-sak-web/prosess-uttak'; import { useRestApiErrorDispatcher } from '@k9-sak-web/rest-api-hooks'; import { Aksjonspunkt, AlleKodeverk, ArbeidsgiverOpplysningerPerId, Behandling } from '@k9-sak-web/types'; +import VurderOverlappendeSakIndex from '@k9-sak-web/gui/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSakIndex.js'; import { OverstyringUttakRequest } from '../types'; +import { konverterKodeverkTilKode } from '@k9-sak-web/lib/kodeverk/konverterKodeverkTilKode.js'; +import { VStack } from '@navikt/ds-react'; +import FeatureTogglesContext from '@k9-sak-web/gui/utils/featureToggles/FeatureTogglesContext.js'; interface UttakProps { uuid: string; @@ -42,6 +47,7 @@ export default ({ erOverstyrer, readOnly, }: UttakProps) => { + const featureToggles = useContext(FeatureTogglesContext); const { versjon, links, status: behandlingStatus } = behandling; const { addErrorMessage } = useRestApiErrorDispatcher(); const httpErrorHandlerCaller = (status: number, locationHeader?: string) => @@ -65,6 +71,30 @@ export default ({ }); }; + const VurderOverlappendeSakComponent = () => { + const aksjonspunkt = aksjonspunkter.find( + aksjonspunkt => aksjonspunktCodes.VURDER_OVERLAPPENDE_SØSKENSAK_KODE === aksjonspunkt.definisjon.kode, + ); + + if (featureToggles.AKSJONSPUNKT_OVERLAPPENDE_SAKER && aksjonspunkt) { + const deepCopyProps = JSON.parse( + JSON.stringify({ + behandling: behandling, + aksjonspunkt: aksjonspunkt, + }), + ); + konverterKodeverkTilKode(deepCopyProps, false); + + return ( + + + + ); + } + + return <>; + }; + return ( ); diff --git a/packages/behandling-pleiepenger/src/panelDefinisjoner/prosessStegPaneler/UttakProsessStegPanelDef.tsx b/packages/behandling-pleiepenger/src/panelDefinisjoner/prosessStegPaneler/UttakProsessStegPanelDef.tsx index ba92681a16..52439e6669 100644 --- a/packages/behandling-pleiepenger/src/panelDefinisjoner/prosessStegPaneler/UttakProsessStegPanelDef.tsx +++ b/packages/behandling-pleiepenger/src/panelDefinisjoner/prosessStegPaneler/UttakProsessStegPanelDef.tsx @@ -45,6 +45,7 @@ class PanelDef extends ProsessStegPanelDef { aksjonspunktCodes.VENT_ANNEN_PSB_SAK, aksjonspunktCodes.VURDER_DATO_NY_REGEL_UTTAK, aksjonspunktCodes.OVERSTYRING_AV_UTTAK_KODE, + aksjonspunktCodes.VURDER_OVERLAPPENDE_SØSKENSAK_KODE, ]; getOverstyrVisningAvKomponent = () => true; diff --git a/packages/kodeverk/src/aksjonspunktCodes.ts b/packages/kodeverk/src/aksjonspunktCodes.ts index 90c026279a..8f9d6f4a88 100644 --- a/packages/kodeverk/src/aksjonspunktCodes.ts +++ b/packages/kodeverk/src/aksjonspunktCodes.ts @@ -97,6 +97,7 @@ const aksjonspunktCodes = { VURDER_GJENNOMGÅTT_OPPLÆRING: '9302', VURDER_REISETID: '9303', VURDER_DATO_NY_REGEL_UTTAK: '9291', + VURDER_OVERLAPPENDE_SØSKENSAK_KODE: '9292', }; const klageAksjonspunkter = [ diff --git a/packages/prosess-uttak/src/constants/Aksjonspunkter.ts b/packages/prosess-uttak/src/constants/Aksjonspunkter.ts index 70078ec5ba..e69fa5b100 100644 --- a/packages/prosess-uttak/src/constants/Aksjonspunkter.ts +++ b/packages/prosess-uttak/src/constants/Aksjonspunkter.ts @@ -1,2 +1,3 @@ export const aksjonspunktkodeVentAnnenPSBSakKode = '9290'; export const aksjonspunktVurderDatoKode = '9291'; +export const aksjonspunktVurderOverlappendeYtelsekode = '9292'; diff --git a/packages/prosess-uttak/src/types/ContainerContract.ts b/packages/prosess-uttak/src/types/ContainerContract.ts index 551e102fd3..4c2cacae80 100644 --- a/packages/prosess-uttak/src/types/ContainerContract.ts +++ b/packages/prosess-uttak/src/types/ContainerContract.ts @@ -4,6 +4,7 @@ import ArbeidsgiverOpplysninger from './ArbeidsgiverOpplysninger'; import KodeverkMedNavn from './kodeverkMedNavnTsType'; import Uttaksperioder from './Uttaksperioder'; import { Inntektsgradering } from '.'; +import { ReactNode } from 'react'; interface ContainerContract { httpErrorHandler?: (status: number, locationHeader?: string) => void; @@ -34,6 +35,7 @@ interface ContainerContract { erOverstyrer?: boolean; status?: string | false; readOnly: boolean; + vurderOverlappendeSakComponent?: ReactNode; } export default ContainerContract; diff --git a/packages/prosess-uttak/src/ui/UttakContainer.tsx b/packages/prosess-uttak/src/ui/UttakContainer.tsx index d0535af67f..077fad08c7 100644 --- a/packages/prosess-uttak/src/ui/UttakContainer.tsx +++ b/packages/prosess-uttak/src/ui/UttakContainer.tsx @@ -1,4 +1,4 @@ -import { HStack, Heading } from '@navikt/ds-react'; +import { HStack, Heading, VStack } from '@navikt/ds-react'; import { OverstyringKnapp } from '@navikt/ft-ui-komponenter'; import React from 'react'; import { aksjonspunktVurderDatoKode, aksjonspunktkodeVentAnnenPSBSakKode } from '../constants/Aksjonspunkter'; @@ -27,6 +27,7 @@ const UttakContainer = ({ containerData }: MainComponentProps): JSX.Element => { virkningsdatoUttakNyeRegler, erOverstyrer, readOnly, + vurderOverlappendeSakComponent, } = containerData; const [redigerVirkningsdato, setRedigervirkningsdato] = React.useState(false); const aksjonspunktVurderDato = aksjonspunkter?.find(ap => ap.definisjon.kode === aksjonspunktVurderDatoKode); @@ -44,43 +45,48 @@ const UttakContainer = ({ containerData }: MainComponentProps): JSX.Element => { return ( - - - Uttak - - {erOverstyrer && } - + + + + Uttak + + {erOverstyrer && } + + - + {vurderOverlappendeSakComponent && ( +
{vurderOverlappendeSakComponent}
+ )} - - - + + + - - {/* Allerede løst og har klikket rediger, eller har uløst aksjonspunkt */} - {((virkningsdatoUttakNyeRegler && redigerVirkningsdato) || - harAksjonspunktVurderDatoMedStatusOpprettet || - (readOnly && aksjonspunktVurderDato)) && ( - setRedigervirkningsdato(false) : undefined - } - initialValues={{ - begrunnelse: aksjonspunktVurderDato?.begrunnelse, - virkningsdato: virkningsdatoUttakNyeRegler, - }} - readOnly={readOnly} - /> - )} - {!harVentAnnenPSBSakAksjonspunkt && ( - setRedigervirkningsdato(true)} - redigerVirkningsdato={redigerVirkningsdato} - readOnly={readOnly} - /> - )} + + {/* Allerede løst og har klikket rediger, eller har uløst aksjonspunkt */} + {((virkningsdatoUttakNyeRegler && redigerVirkningsdato) || + harAksjonspunktVurderDatoMedStatusOpprettet || + (readOnly && aksjonspunktVurderDato)) && ( + setRedigervirkningsdato(false) : undefined + } + initialValues={{ + begrunnelse: aksjonspunktVurderDato?.begrunnelse, + virkningsdato: virkningsdatoUttakNyeRegler, + }} + readOnly={readOnly} + /> + )} + {!harVentAnnenPSBSakAksjonspunkt && ( + setRedigervirkningsdato(true)} + redigerVirkningsdato={redigerVirkningsdato} + readOnly={readOnly} + /> + )} +
); }; diff --git a/packages/prosess-uttak/src/ui/components/uttak/Uttak.tsx b/packages/prosess-uttak/src/ui/components/uttak/Uttak.tsx index 4bb264fa4f..859ae77522 100644 --- a/packages/prosess-uttak/src/ui/components/uttak/Uttak.tsx +++ b/packages/prosess-uttak/src/ui/components/uttak/Uttak.tsx @@ -59,7 +59,7 @@ const Uttak = ({ uttak, erValgt, velgPeriode, withBorderTop = false }: UttakProp {periode.getFirstAndLastWeek()} - + {periode.prettifyPeriod()} {manueltOverstyrt && ( <> diff --git a/packages/prosess-uttak/src/ui/mainComponent.module.css b/packages/prosess-uttak/src/ui/mainComponent.module.css index 1b051f1bb7..848f595fc3 100644 --- a/packages/prosess-uttak/src/ui/mainComponent.module.css +++ b/packages/prosess-uttak/src/ui/mainComponent.module.css @@ -1,3 +1,7 @@ .overstyringsHeader > button { cursor: pointer; } + +.overlappendeSakContainer { + max-width: var(--a-breakpoint-md); +} diff --git a/packages/v2/backend/package.json b/packages/v2/backend/package.json index 5a5efc2728..3f513b9b8b 100644 --- a/packages/v2/backend/package.json +++ b/packages/v2/backend/package.json @@ -16,7 +16,7 @@ }, "dependencies": { "@navikt/k9-klage-typescript-client": "1.0.20241206133624", - "@navikt/k9-sak-typescript-client": "1.0.20241203201014" + "@navikt/k9-sak-typescript-client": "1.0.20250102132051" }, "devDependencies": { "@tanstack/eslint-plugin-query": "^5.62.1" diff --git a/packages/v2/gui/package.json b/packages/v2/gui/package.json index 394819716c..fb49e08622 100644 --- a/packages/v2/gui/package.json +++ b/packages/v2/gui/package.json @@ -17,12 +17,14 @@ "@hookform/error-message": "^2.0.1", "@k9-sak-web/backend": "workspace:^", "@k9-sak-web/lib": "1.0.0", + "@navikt/ft-form-hooks": "5.2.9", "@navikt/ft-form-validators": "2.5.7", "@navikt/ft-utils": "2.5.4", "@tanstack/react-query": "^5.62.8", "@types/object-hash": "^3.0.6", "axios": "1.7.9", "object-hash": "^3.0.0", - "react": "18.3.1" + "react": "18.3.1", + "react-hook-form": "7.53.2" } } diff --git a/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendApiType.ts b/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendApiType.ts new file mode 100644 index 0000000000..5ca188c227 --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendApiType.ts @@ -0,0 +1,6 @@ +import type { BekreftData, BekreftResponse, EgneOverlappendeSakerDto } from '@k9-sak-web/backend/k9sak/generated'; + +export type BehandlingUttakBackendApiType = { + getEgneOverlappendeSaker(behandlingUuid: string): Promise; + bekreftAksjonspunkt(requestBody: BekreftData['requestBody']): Promise; +}; diff --git a/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendClient.ts b/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendClient.ts new file mode 100644 index 0000000000..8ad0a88e11 --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/BehandlingUttakBackendClient.ts @@ -0,0 +1,22 @@ +import type { + K9SakClient, + EgneOverlappendeSakerDto, + BekreftResponse, + BekreftData, +} from '@k9-sak-web/backend/k9sak/generated'; + +export default class BehandlingUttakBackendClient { + #k9sak: K9SakClient; + + constructor(k9sakClient: K9SakClient) { + this.#k9sak = k9sakClient; + } + + async getEgneOverlappendeSaker(behandlingUuid: string): Promise { + return this.#k9sak.behandlingUttak.hentEgneOverlappendeSaker(behandlingUuid); + } + + async bekreftAksjonspunkt(requestBody: BekreftData['requestBody']): Promise { + return this.#k9sak.aksjonspunkt.bekreft(requestBody); + } +} diff --git a/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.css b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.css new file mode 100644 index 0000000000..59dd000d15 --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.css @@ -0,0 +1,35 @@ +.vurderOverlappendeSak { + max-width: 43.5rem; +} + +.apContainer { + border-radius: 3px; + padding: var(--a-spacing-5); +} + +.apActive { + border: 2px solid var(--a-orange-500); +} + +.apReadOnly { + border-radius: 3px; + border: 1px solid var(--a-border-default); +} + +.apContainer a { + color: var(--a-text-default); +} + +.apContainer ul { + margin: 0; + padding-left: var(--a-spacing-6) 0; +} + +.apContainer li { + list-style-position: inside; + padding-left: var(--a-spacing-2); +} + +.uttaksgradField input { + max-width: var(--a-spacing-12); +} diff --git a/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.d.css.ts b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.d.css.ts new file mode 100644 index 0000000000..ef62948b0c --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.module.d.css.ts @@ -0,0 +1,9 @@ +declare const styles: { + readonly "apActive": string; + readonly "apContainer": string; + readonly "apReadOnly": string; + readonly "uttaksgradField": string; + readonly "vurderOverlappendeSak": string; +}; +export = styles; + diff --git a/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.stories.tsx b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.stories.tsx new file mode 100644 index 0000000000..a68b126ef8 --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.stories.tsx @@ -0,0 +1,279 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import type { AksjonspunktDto, BehandlingDto, EgneOverlappendeSakerDto } from '@k9-sak-web/backend/k9sak/generated'; +import VurderOverlappendeSak, { type BekreftVurderOverlappendeSakerAksjonspunktRequest } from './VurderOverlappendeSak'; +import { FakeBehandlingUttakBackendApi } from '../../../storybook/mocks/FakeBehandlingUttakBackendApi'; +import { formatDate, subDays, subWeeks } from 'date-fns'; +import { userEvent, within, expect, fireEvent, fn } from '@storybook/test'; +import { HStack } from '@navikt/ds-react'; + +const fom1 = subWeeks(new Date(), 4); +const tom1 = subWeeks(new Date(), 3); +const fom2 = subWeeks(new Date(), 2); +const tom2 = subWeeks(new Date(), 1); + +const førstePeriodeMedOverlapp = { + periode: { fom: fom1.toISOString(), tom: tom1.toISOString() }, + skalVurderes: true, + saksnummer: ['ABCDE'], +}; + +const andrePeriodeMedOverlapp = { + periode: { fom: fom2.toISOString(), tom: tom2.toISOString() }, + skalVurderes: true, + saksnummer: ['FGHIJ'], +}; + +const egneOverlappendeSakerDtoer = [ + { + // array index 0 matcher med sak med uuid 0 i testdataene + perioderMedOverlapp: [ + { + fastsattUttaksgrad: null, + saksbehandler: null, + vurdertTidspunkt: null, + ...førstePeriodeMedOverlapp, + }, + { + fastsattUttaksgrad: null, + saksbehandler: null, + vurdertTidspunkt: null, + ...andrePeriodeMedOverlapp, + }, + ], + }, + { + // array index 1 matcher med sak med uuid 1 i testdataene + perioderMedOverlapp: [ + { + fastsattUttaksgrad: 60.0, + saksbehandler: 'Sara Sak', + vurdertTidspunkt: subDays(new Date(), 2).toISOString(), + ...førstePeriodeMedOverlapp, + }, + { + fastsattUttaksgrad: 70.0, + saksbehandler: 'Sara Sak', + vurdertTidspunkt: subDays(new Date(), 2).toISOString(), + ...andrePeriodeMedOverlapp, + }, + ], + }, +]; + +const bekreftAksjonspunktRequest: BekreftVurderOverlappendeSakerAksjonspunktRequest = { + behandlingId: '123', + behandlingVersjon: 1, + bekreftedeAksjonspunktDtoer: [ + { + '@type': '9292', + kode: '9292', + begrunnelse: 'Dette er en grundig begrunnelse', + perioder: [ + { + // k9-sak påkrever begrunnelse i hver periode, og det kan ikke være en tom streng + begrunnelse: 'Dette er en grundig begrunnelse', + periode: { fom: fom1.toISOString(), tom: tom1.toISOString() }, + søkersUttaksgrad: '40', + }, + { + begrunnelse: 'Dette er en grundig begrunnelse', + periode: { fom: fom2.toISOString(), tom: tom2.toISOString() }, + søkersUttaksgrad: '60', + }, + ], + }, + ], +}; + +const api = new FakeBehandlingUttakBackendApi(egneOverlappendeSakerDtoer as EgneOverlappendeSakerDto[]); +const meta = { + title: 'gui/prosess/Uttak/Overlappende-Saker', + component: VurderOverlappendeSak, + beforeEach: () => { + api.reset(); + }, +} satisfies Meta; + +type Story = StoryObj; + +const behandling: Omit = { + opprettet: subDays(new Date(), 5).toISOString(), + sakstype: 'PSB', + type: 'BT-002', + versjon: 1, + id: 123, +}; + +const uløstBehandling: BehandlingDto = { + uuid: '0', // match med array index for egneOverlappendeSaker endepunktet for å returnere riktig overlappende saker + status: 'UTRED', + ...behandling, +}; + +const løstBehandling: BehandlingDto = { + uuid: '1', // match med array index for egneOverlappendeSaker endepunktet for å returnere riktig overlappende saker + status: 'UTRED', + ...behandling, +}; + +const avsluttetBehandling: BehandlingDto = { + uuid: '1', // match med array index for egneOverlappendeSaker endepunktet for å returnere riktig overlappende saker + status: 'AVSLU', + ...behandling, +}; + +const aksjonspunkt: AksjonspunktDto = { + aksjonspunktType: 'MANU', + erAktivt: true, + besluttersBegrunnelse: null, + definisjon: '9292', + fristTid: null, + toTrinnsBehandling: true, + toTrinnsBehandlingGodkjent: null, + vilkarType: null, + vurderPaNyttArsaker: null, + venteårsak: '-', + venteårsakVariant: null, + opprettetAv: 'vtp', +}; + +const uløstAksjonspunkt: AksjonspunktDto = { + begrunnelse: null, + kanLoses: true, + status: 'OPPR', + ...aksjonspunkt, +}; + +const løstAksjonspunkt: AksjonspunktDto = { + begrunnelse: 'Dette er en grundig begrunnelse', + kanLoses: false, + status: 'UTFO', + ...aksjonspunkt, +}; + +const løstAksjonspunktFerdigstilt: AksjonspunktDto = { + begrunnelse: 'Dette er en grundig begrunnelse', + kanLoses: false, + status: 'UTFO', + ...aksjonspunkt, +}; + +const oppdaterBehandling = fn(); + +const feltEnLabel = `Sett uttaksgrad i prosent for perioden ${formatDate(fom1, 'dd.MM.yyyy')} - ${formatDate(tom1, 'dd.MM.yyyy')}`; +const feltToLabel = `Sett uttaksgrad i prosent for perioden ${formatDate(fom2, 'dd.MM.yyyy')} - ${formatDate(tom2, 'dd.MM.yyyy')}`; + +export const Aksjonspunkt: Story = { + args: { behandling: uløstBehandling, aksjonspunkt: uløstAksjonspunkt, api, oppdaterBehandling }, + play: async ({ args, canvasElement, step }) => { + const canvas = within(canvasElement); + + await step('skal ha skjema for å sette uttaksgrad for overlappende saker', async () => { + await expect(canvas.getByText('Uttaksgrad for overlappende perioder')); + await expect(await canvas.findByLabelText(feltEnLabel)); + await expect(await canvas.findByLabelText(feltToLabel)); + }); + + await step('skal kunne løse aksjonspunkt for overlappende saker', async () => { + /** + * .type() ser ikke ut til å oppdatere value på elementene riktig, bruker fireEvent isteden + * await userEvent.type(canvas.getByLabelText(feltEnLabel), '40'); + * */ + await fireEvent.change(await canvas.getByLabelText(feltEnLabel), { + target: { value: bekreftAksjonspunktRequest.bekreftedeAksjonspunktDtoer[0]?.perioder[0]?.søkersUttaksgrad }, + }); + await fireEvent.change(await canvas.getByLabelText(feltToLabel), { + target: { value: bekreftAksjonspunktRequest.bekreftedeAksjonspunktDtoer[0]?.perioder[1]?.søkersUttaksgrad }, + }); + await fireEvent.change(await canvas.getByLabelText('Begrunnelse'), { + target: { + value: 'Dette er en grundig begrunnelse', + }, + }); + + await userEvent.click(await canvas.getByRole('button')); + await expect(args.oppdaterBehandling).toHaveBeenCalled(); + await expect(api.sisteBekreftAksjonspunktResultat).toEqual(bekreftAksjonspunktRequest); + }); + }, + render: props => ( + + + + ), +}; + +export const LøstAksjonspunktKanRedigeres: Story = { + args: { behandling: løstBehandling, aksjonspunkt: løstAksjonspunkt, api, oppdaterBehandling: fn() }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step('skal vise lesevisning av skjema for uttaksgrad for overlappende saker', async () => { + const feltEn = await canvas.findByLabelText(feltEnLabel); + const feltTo = await canvas.findByLabelText(feltToLabel); + const begrunnelseFelt = await canvas.getByLabelText('Begrunnelse'); + const knapp = await canvas.getByRole('button'); + + await expect(canvas.getByText('Uttaksgrad for overlappende perioder')); + + await expect(feltEn); + await expect(await canvas.findByLabelText(feltEnLabel)).toHaveValue('60'); + await expect(await canvas.findByLabelText(feltEnLabel)).toHaveAttribute('readonly'); + + await expect(feltTo); + await expect(feltTo).toHaveValue('70'); + await expect(feltTo).toHaveAttribute('readonly'); + + await expect(begrunnelseFelt).toHaveValue('Dette er en grundig begrunnelse'); + await expect(begrunnelseFelt).toHaveAttribute('readonly'); + + await expect(knapp).toHaveTextContent('Rediger'); + await userEvent.click(knapp); + await expect(feltEn.getAttribute('readonly')).toEqual(''); + await expect(feltTo.getAttribute('readonly')).toEqual(''); + await expect(begrunnelseFelt.getAttribute('readonly')).toEqual(''); + + await userEvent.click(await canvas.getByRole('button', { name: /Avbryt/i })); + await expect(feltTo).toHaveAttribute('readonly'); + await expect(feltTo).toHaveAttribute('readonly'); + }); + }, + render: props => ( + + + + ), +}; + +export const LøstAksjonspunktAvsluttetSak: Story = { + args: { behandling: avsluttetBehandling, aksjonspunkt: løstAksjonspunktFerdigstilt, api, oppdaterBehandling: fn() }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step('skal vise leseversjon av skjema i avsluttet sak', async () => { + const feltEn = await canvas.findByLabelText(feltEnLabel); + const feltTo = await canvas.findByLabelText(feltToLabel); + const begrunnelseFelt = await canvas.getByLabelText('Begrunnelse'); + + await expect(canvas.getByText('Uttaksgrad for overlappende perioder')); + + await expect(feltEn); + await expect(feltEn).toHaveValue('60'); + await expect(feltEn).toHaveAttribute('readonly'); + + await expect(feltTo); + await expect(feltTo).toHaveValue('70'); + await expect(feltTo).toHaveAttribute('readonly'); + + await expect(begrunnelseFelt).toHaveValue('Dette er en grundig begrunnelse'); + await expect(begrunnelseFelt).toHaveAttribute('readonly'); + }); + }, + render: props => ( + + + + ), +}; + +export default meta; diff --git a/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.tsx b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.tsx new file mode 100644 index 0000000000..eaad170ac7 --- /dev/null +++ b/packages/v2/gui/src/prosess/uttak/vurder-overlappende-sak/VurderOverlappendeSak.tsx @@ -0,0 +1,268 @@ +import React, { useEffect, useState, type FC } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { useFieldArray, useForm } from 'react-hook-form'; +import { + Alert, + BodyShort, + Box, + Button, + Heading, + HStack, + List, + Loader, + Textarea, + TextField, + VStack, +} from '@navikt/ds-react'; +import { Form, NumberField, TextAreaField } from '@navikt/ft-form-hooks'; +import { AssessedBy } from '@navikt/ft-plattform-komponenter'; +import { hasValidInteger, maxLength, minLength, required } from '@navikt/ft-form-validators'; +import { formatPeriod } from '@k9-sak-web/lib/dateUtils/dateUtils.js'; +import type { + AksjonspunktDto, + BehandlingDto, + BekreftData, + EgneOverlappendeSakerDto, +} from '@k9-sak-web/backend/k9sak/generated'; +import type { BehandlingUttakBackendApiType } from '../BehandlingUttakBackendApiType'; +import { erAksjonspunktReadOnly, kanAksjonspunktRedigeres } from '../../../utils/aksjonspunkt'; + +import styles from './VurderOverlappendeSak.module.css'; + +interface Props { + behandling: Pick; + aksjonspunkt: AksjonspunktDto; + api: BehandlingUttakBackendApiType; + oppdaterBehandling: () => void; +} + +export interface VurderOverlappendeSakFormData { + begrunnelse: string; + perioder: { + periode: { fom: string; tom: string }; + søkersUttaksgrad: number; + saksnummer: string[]; + }[]; +} + +export type BekreftVurderOverlappendeSakerAksjonspunktRequest = BekreftData['requestBody'] & { + bekreftedeAksjonspunktDtoer: Array<{ + '@type': string; + kode: string | null | undefined; + perioder: Array<{ + begrunnelse: string; + periode: { fom: string; tom: string }; + søkersUttaksgrad: number | string; + }>; + }>; +}; + +const VurderOverlappendeSak: FC = ({ behandling, aksjonspunkt, api, oppdaterBehandling }) => { + const [loading, setLoading] = useState(false); + const { uuid, id, versjon, status } = behandling; + const [readOnly, setReadOnly] = useState(erAksjonspunktReadOnly(aksjonspunkt)); + const [rediger, setRediger] = useState(false); + const sakAvsluttet = status === 'AVSLU'; + + const buildInitialValues = (data: EgneOverlappendeSakerDto | undefined): VurderOverlappendeSakFormData => { + return { + begrunnelse: aksjonspunkt?.begrunnelse || '', + perioder: + data?.perioderMedOverlapp.map(periode => ({ + periode: { fom: periode.periode.fom || '', tom: periode.periode.tom || '' }, + søkersUttaksgrad: periode.fastsattUttaksgrad, + saksnummer: periode.saksnummer.map(saksNr => saksNr || ''), + })) || [], + }; + }; + + const { + data: egneOverlappendeSaker, + isLoading: overlappendeIsLoading, + isSuccess: overlappendeSuccess, + isError: overlappendeIsError, + } = useQuery({ + queryKey: ['overlappende', uuid], + queryFn: async () => await api.getEgneOverlappendeSaker(uuid), + }); + + const formMethods = useForm({ + defaultValues: { + perioder: [], + }, + }); + + const { reset, control, register } = formMethods; + const { fields } = useFieldArray({ control, name: 'perioder' }); + + useEffect(() => { + if (overlappendeSuccess && egneOverlappendeSaker) { + const newValues = buildInitialValues(egneOverlappendeSaker); + reset(newValues); + } + }, [overlappendeSuccess, egneOverlappendeSaker, reset]); + + const submit = async (data: VurderOverlappendeSakFormData) => { + setLoading(true); + const requestBody: BekreftVurderOverlappendeSakerAksjonspunktRequest = { + behandlingId: `${id}`, + behandlingVersjon: versjon, + bekreftedeAksjonspunktDtoer: [ + { + '@type': aksjonspunkt.definisjon || '', + kode: aksjonspunkt.definisjon, + begrunnelse: data.begrunnelse, + perioder: data.perioder.map(periode => ({ + begrunnelse: data.begrunnelse, + periode: { fom: periode.periode.fom || '', tom: periode.periode.tom || '' }, + søkersUttaksgrad: periode.søkersUttaksgrad, + })), + }, + ], + }; + api.bekreftAksjonspunkt(requestBody).then(() => { + setLoading(false); + oppdaterBehandling(); + }); + }; + + if (overlappendeIsError) { + return Noe gikk galt, vennligst prøv igjen senere; + } + + const toggleRediger = () => { + setReadOnly(!readOnly); + setRediger(!rediger); + }; + + const saksbehandler = + egneOverlappendeSaker?.perioderMedOverlapp.find(periode => periode.saksbehandler)?.saksbehandler || undefined; + + const vurdertTidspunkt = + egneOverlappendeSaker?.perioderMedOverlapp.find(periode => periode.vurdertTidspunkt)?.vurdertTidspunkt || undefined; + + return ( + + {!readOnly && ( + + + Vurder overlappende perioder med annen sak + + + Søker har overlappende perioder i uttak med annen sak. Fordel uttaksgrad i begge saker, så den totale + uttaksgraden ikke overstiger 100 %. + + + )} + + +
+ + Uttaksgrad for overlappende perioder + {overlappendeIsLoading && } + {overlappendeSuccess && ( + <> + + Perioder som overlapper med sak: + {egneOverlappendeSaker?.perioderMedOverlapp.map(periodeMedOverlapp => { + const { + periode: { fom, tom }, + saksnummer, + } = periodeMedOverlapp; + return ( + + + + + {formatPeriod(fom || '', tom || '')} ( + {saksnummer.map(sakNr => ( + + {sakNr} + + ))} + ) + + + + + ); + })} + + {fields.map((field, index) => { + const { + periode: { fom, tom }, + saksnummer, + } = field; + return ( +
+ {readOnly ? ( + + ) : ( + + )} +
+ ); + })} + {readOnly ? ( +