diff --git a/apps/judicial-system/api/infra/judicial-system-api.ts b/apps/judicial-system/api/infra/judicial-system-api.ts index ca299773ddb7..1b3197143591 100644 --- a/apps/judicial-system/api/infra/judicial-system-api.ts +++ b/apps/judicial-system/api/infra/judicial-system-api.ts @@ -46,9 +46,9 @@ export const serviceSetup = (services: { prod: 'master', }, HIDDEN_FEATURES: { - dev: '', - staging: '', - prod: '', + dev: 'OFFENSE_ENDPOINTS', + staging: 'OFFENSE_ENDPOINTS', + prod: 'OFFENSE_ENDPOINTS', }, }) .secrets({ diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx index e14f1825769c..c0fb2ffb14be 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Defendant/Defendant.tsx @@ -9,11 +9,13 @@ import * as constants from '@island.is/judicial-system/consts' import { CrimeScene, CrimeSceneMap, + Feature, IndictmentSubtype, IndictmentSubtypeMap, } from '@island.is/judicial-system/types' import { core, errors, titles } from '@island.is/judicial-system-web/messages' import { + FeatureContext, FormContentContainer, FormContext, FormFooter, @@ -38,7 +40,7 @@ import { isDefendantStepValidIndictments } from '@island.is/judicial-system-web/ import { DefendantInfo, ProsecutorSection } from '../../components' import { getIndictmentIntroductionAutofill } from '../Indictment/Indictment' -import { getIncidentDescription } from '../Indictment/IndictmentCount' +import { getIncidentDescription } from '../Indictment/lib/getIncidentDescription' import { LokeNumberList } from './LokeNumberList/LokeNumberList' import { PoliceCaseInfo } from './PoliceCaseInfo/PoliceCaseInfo' import { usePoliceCaseInfoQuery } from './policeCaseInfo.generated' @@ -102,6 +104,9 @@ const getPoliceCasesForUpdate = ( ) const Defendant = () => { + const { features } = useContext(FeatureContext) + const isOffenseEndpointEnabled = features.includes(Feature.OFFENSE_ENDPOINTS) + const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) const { formatMessage } = useIntl() @@ -299,12 +304,13 @@ const Defendant = () => { ...indictmentCount, indictmentCountSubtypes: subtypes?.[policeCaseNumber], } - const incidentDescription = getIncidentDescription( - updatedIndictmentCount, + const incidentDescription = getIncidentDescription({ + indictmentCount: updatedIndictmentCount, formatMessage, crimeScene, - subtypes, - ) + subtypesRecord: subtypes, + isOffenseEndpointEnabled, + }) updateIndictmentCount(workingCase.id, indictmentCount.id, { incidentDescription, diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx index cb7f1ebec691..fa5db9b90af8 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx @@ -7,9 +7,11 @@ import router from 'next/router' import { Box, Button, Checkbox, Input } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' import { formatNationalId } from '@island.is/judicial-system/formatters' +import { Feature } from '@island.is/judicial-system/types' import { titles } from '@island.is/judicial-system-web/messages' import { BlueBox, + FeatureContext, FormContentContainer, FormContext, FormFooter, @@ -26,6 +28,7 @@ import { IndictmentCountOffense, Institution, Maybe, + Offense, PoliceCaseInfo, } from '@island.is/judicial-system-web/src/graphql/schema' import { TempIndictmentCount as TIndictmentCount } from '@island.is/judicial-system-web/src/types' @@ -78,6 +81,9 @@ export const getIndictmentIntroductionAutofill = ( } const Indictment = () => { + const { features } = useContext(FeatureContext) + const isOffenseEndpointEnabled = features.includes(Feature.OFFENSE_ENDPOINTS) + const { workingCase, setWorkingCase, @@ -85,6 +91,7 @@ const Indictment = () => { caseNotFound, isCaseUpToDate, } = useContext(FormContext) + const { formatMessage } = useIntl() const { updateCase, setAndSendCaseToServer } = useCase() const { @@ -115,7 +122,7 @@ const Indictment = () => { }, }) - const stepIsValid = isIndictmentStepValid(workingCase) + const stepIsValid = isIndictmentStepValid(workingCase, isOffenseEndpointEnabled) const handleNavigationTo = useCallback( (destination: string) => router.push(`${destination}/${workingCase.id}`), @@ -129,16 +136,25 @@ const Indictment = () => { // If the case has: // at least one count with the offense driving under the influence of alcohol, illegal drugs or prescription drugs // then by default the prosecutor requests a suspension of the driver's licence. - const requestDriversLicenseSuspension = indictmentCounts?.some((count) => - count.deprecatedOffenses?.some((offense) => - [ - IndictmentCountOffense.DRUNK_DRIVING, - IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, - IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, - ].includes(offense), - ), - ) - + const requestDriversLicenseSuspension = isOffenseEndpointEnabled + ? indictmentCounts?.some((count) => { + return count.offenses?.some((o) => + [ + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ].includes(o.offense), + ) + }) + : indictmentCounts?.some((count) => + count.deprecatedOffenses?.some((offense) => + [ + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ].includes(offense), + ), + ) if ( requestDriversLicenseSuspension !== workingCase.requestDriversLicenseSuspension @@ -158,7 +174,13 @@ const Indictment = () => { ) } }, - [formatMessage, setAndSendCaseToServer, setWorkingCase, workingCase], + [ + isOffenseEndpointEnabled, + formatMessage, + setAndSendCaseToServer, + setWorkingCase, + workingCase, + ], ) const handleCreateIndictmentCount = useCallback(async () => { @@ -191,6 +213,7 @@ const Indictment = () => { async ( indictmentCountId: string, updatedIndictmentCount: UpdateIndictmentCount, + updatedOffenses?: Offense[], ) => { if ( updatedIndictmentCount.policeCaseNumber && @@ -218,7 +241,9 @@ const Indictment = () => { setDriversLicenseSuspensionRequest( workingCase.indictmentCounts?.map((count) => - count.id === indictmentCountId ? returnedIndictmentCount : count, + count.id === indictmentCountId + ? { ...returnedIndictmentCount, offenses: updatedOffenses } + : count, ), ) @@ -226,6 +251,7 @@ const Indictment = () => { indictmentCountId, returnedIndictmentCount, setWorkingCase, + updatedOffenses, ) }, [ diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.spec.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.spec.tsx index a529b583e6db..12d3c7e26508 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.spec.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.spec.tsx @@ -1,17 +1,9 @@ import { createIntl } from 'react-intl' import { Substance, SubstanceMap } from '@island.is/judicial-system/types' -import { - IndictmentCountOffense as offense, - IndictmentSubtype, -} from '@island.is/judicial-system-web/src/graphql/schema' +import { IndictmentCountOffense as offense } from '@island.is/judicial-system-web/src/graphql/schema' -import { - getIncidentDescription, - getIncidentDescriptionReason, - getLegalArguments, - getRelevantSubstances, -} from './IndictmentCount' +import { getLegalArguments, getRelevantSubstances } from './IndictmentCount' const formatMessage = createIntl({ locale: 'is', @@ -132,249 +124,3 @@ describe('getLegalArguments', () => { ) }) }) - -describe('getIncidentDescriptionReason', () => { - test('should return a description for one offense', () => { - const deprecatedOffenses = [offense.DRIVING_WITHOUT_LICENCE] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe('sviptur ökurétti') - }) - - test('should return a description for two offense', () => { - const deprecatedOffenses = [ - offense.DRIVING_WITHOUT_LICENCE, - offense.DRUNK_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe('sviptur ökurétti og undir áhrifum áfengis') - }) - - test('should return a description with prescription drugs', () => { - const deprecatedOffenses = [ - offense.DRUNK_DRIVING, - offense.PRESCRIPTION_DRUGS_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe( - 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa slævandi lyfja', - ) - }) - - test('should return a description with illegal drugs', () => { - const deprecatedOffenses = [ - offense.DRUNK_DRIVING, - offense.ILLEGAL_DRUGS_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe( - 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', - ) - }) - - test('should return a description with illegal drugs as third offense', () => { - const deprecatedOffenses = [ - offense.DRIVING_WITHOUT_LICENCE, - offense.DRUNK_DRIVING, - offense.ILLEGAL_DRUGS_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe( - 'sviptur ökurétti, undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', - ) - }) - - test('should return a description with illegal and prescription drugs', () => { - const deprecatedOffenses = [ - offense.DRUNK_DRIVING, - offense.ILLEGAL_DRUGS_DRIVING, - offense.PRESCRIPTION_DRUGS_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe( - 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', - ) - }) - - test('should return a description with only illegal and prescription drugs', () => { - const deprecatedOffenses = [ - offense.ILLEGAL_DRUGS_DRIVING, - offense.PRESCRIPTION_DRUGS_DRIVING, - ] - - const result = getIncidentDescriptionReason( - deprecatedOffenses, - {}, - formatMessage, - ) - - expect(result).toBe( - 'óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', - ) - }) -}) - -describe('getIncidentDescription', () => { - test('should return an empty string if there are no deprecatedOffenses in traffic violations', () => { - const result = getIncidentDescription( - { id: 'testId', deprecatedOffenses: [], policeCaseNumber: '123-123-123' }, - formatMessage, - {}, - { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, - ) - - expect(result).toBe('') - }) - - test('should return an empty string if deprecatedOffenses are missing in traffic violations', () => { - const result = getIncidentDescription( - { id: 'testId', policeCaseNumber: '123-123-123' }, - formatMessage, - {}, - { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, - ) - - expect(result).toBe('') - }) - - test('should return a description for only traffic violations', () => { - const result = getIncidentDescription( - { - id: 'testId', - deprecatedOffenses: [offense.DRUNK_DRIVING], - policeCaseNumber: '123-123-123', - }, - formatMessage, - {}, - { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, - ) - - expect(result).toBe( - 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', - ) - }) - - test('should return a description for a single subtype that is not a traffic violation', () => { - const result = getIncidentDescription( - { - id: 'testId', - policeCaseNumber: '123-123-123', - }, - formatMessage, - {}, - { '123-123-123': [IndictmentSubtype.CUSTOMS_VIOLATION] }, - ) - - expect(result).toBe('fyrir [tollalagabrot] með því að hafa, [Dagsetning]') - }) - - test('should return a description when there are multiple subtypes but only traffic violation is selected', () => { - const result = getIncidentDescription( - { - id: 'testId', - policeCaseNumber: '123-123-123', - deprecatedOffenses: [offense.DRUNK_DRIVING], - indictmentCountSubtypes: [IndictmentSubtype.TRAFFIC_VIOLATION], - }, - formatMessage, - {}, - { - '123-123-123': [ - IndictmentSubtype.CUSTOMS_VIOLATION, - IndictmentSubtype.TRAFFIC_VIOLATION, - ], - }, - ) - - expect(result).toBe( - 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', - ) - }) - - test('should return a description when there are multiple subtypes and all are selected', () => { - const result = getIncidentDescription( - { - id: 'testId', - policeCaseNumber: '123-123-123', - deprecatedOffenses: [offense.DRUNK_DRIVING], - indictmentCountSubtypes: [ - IndictmentSubtype.CUSTOMS_VIOLATION, - IndictmentSubtype.THEFT, - ], - }, - formatMessage, - {}, - { - '123-123-123': [ - IndictmentSubtype.CUSTOMS_VIOLATION, - IndictmentSubtype.THEFT, - ], - }, - ) - - expect(result).toBe( - 'fyrir [tollalagabrot, þjófnaður] með því að hafa, [Dagsetning]', - ) - }) - - test('should return the traffic violation description when there are multiple subtypes, all are selected and one is a traffic violation', () => { - const result = getIncidentDescription( - { - id: 'testId', - policeCaseNumber: '123-123-123', - deprecatedOffenses: [offense.DRUNK_DRIVING], - indictmentCountSubtypes: [ - IndictmentSubtype.CUSTOMS_VIOLATION, - IndictmentSubtype.TRAFFIC_VIOLATION, - ], - }, - formatMessage, - {}, - { - '123-123-123': [ - IndictmentSubtype.CUSTOMS_VIOLATION, - IndictmentSubtype.TRAFFIC_VIOLATION, - ], - }, - ) - - expect(result).toBe( - 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', - ) - }) -}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx index 318a63f0bb95..c4133a4c7184 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.tsx @@ -1,4 +1,11 @@ -import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' +import { + Dispatch, + FC, + SetStateAction, + useContext, + useMemo, + useState, +} from 'react' import InputMask from 'react-input-mask' import { IntlShape, useIntl } from 'react-intl' @@ -13,11 +20,10 @@ import { } from '@island.is/island-ui/core' import { capitalize, - formatDate, indictmentSubtypes, } from '@island.is/judicial-system/formatters' import { - CrimeScene, + Feature, IndictmentSubtype, isTrafficViolationCase, offenseSubstances, @@ -26,10 +32,14 @@ import { } from '@island.is/judicial-system/types' import { BlueBox, + FeatureContext, IndictmentInfo, SectionHeading, } from '@island.is/judicial-system-web/src/components' -import { IndictmentCountOffense } from '@island.is/judicial-system-web/src/graphql/schema' +import { + IndictmentCountOffense, + Offense, +} from '@island.is/judicial-system-web/src/graphql/schema' import { TempCase as Case, TempIndictmentCount as TIndictmentCount, @@ -43,11 +53,13 @@ import { UpdateIndictmentCount, useIndictmentCounts, } from '@island.is/judicial-system-web/src/utils/hooks' +import useOffenses from '@island.is/judicial-system-web/src/utils/hooks/useOffenses' -import { Substances as SubstanceChoices } from './Substances/Substances' +import { getIncidentDescription } from './lib/getIncidentDescription' +import { Offenses } from './Offenses/Offenses' +import { DeprecatedSubstances as SubstanceChoices } from './Substances/DeprecatedSubstances' import { indictmentCount as strings } from './IndictmentCount.strings' import { indictmentCountEnum as enumStrings } from './IndictmentCountEnum.strings' -import { indictmentCountSubstanceEnum as substanceStrings } from './IndictmentCountSubstanceEnum.strings' import * as styles from './IndictmentCount.css' interface Props { @@ -57,12 +69,14 @@ interface Props { onChange: ( indictmentCountId: string, updatedIndictmentCount: UpdateIndictmentCount, + updatedOffenses?: Offense[], ) => void onDelete?: (indictmentCountId: string) => Promise updateIndictmentCountState: ( indictmentCountId: string, update: UpdateIndictmentCount, setWorkingCase: Dispatch>, + updatedOffenses?: Offense[], ) => void } @@ -131,16 +145,16 @@ const laws = Object.values(offenseLawsMap) .sort(lawsCompare) const getLawsBroken = ( - deprecatedOffenses?: IndictmentCountOffense[] | null, + offenses?: IndictmentCountOffense[] | null, substances?: SubstanceMap | null, ) => { - if (!deprecatedOffenses || deprecatedOffenses.length === 0) { + if (!offenses || offenses.length === 0) { return [] } let lawsBroken: [number, number][] = [] - deprecatedOffenses.forEach((offense) => { + offenses.forEach((offense) => { lawsBroken = lawsBroken.concat(offenseLawsMap[offense]) if (offense === IndictmentCountOffense.DRUNK_DRIVING) { @@ -180,85 +194,6 @@ export const getRelevantSubstances = ( return relevantSubstances } -export const getIncidentDescriptionReason = ( - deprecatedOffenses: IndictmentCountOffense[], - substances: SubstanceMap, - formatMessage: IntlShape['formatMessage'], -) => { - let reason = deprecatedOffenses - .filter((offense) => offense !== IndictmentCountOffense.SPEEDING) - .reduce((acc, offense, index) => { - if ( - (deprecatedOffenses.length > 1 && - index === deprecatedOffenses.length - 1) || - (deprecatedOffenses.length > 2 && - index === deprecatedOffenses.length - 2 && - offense === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING) - ) { - acc += ' og ' - } else if (index > 0) { - acc += ', ' - } - switch (offense) { - case IndictmentCountOffense.DRIVING_WITHOUT_LICENCE: - acc += formatMessage( - strings.incidentDescriptionDrivingWithoutLicenceAutofill, - ) - break - case IndictmentCountOffense.DRUNK_DRIVING: - acc += formatMessage(strings.incidentDescriptionDrunkDrivingAutofill) - break - case IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING: - acc += `${formatMessage( - strings.incidentDescriptionDrugsDrivingPrefixAutofill, - )} ${formatMessage( - strings.incidentDescriptionIllegalDrugsDrivingAutofill, - )}` - break - case IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING: - acc += - (deprecatedOffenses.includes( - IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, - ) - ? '' - : `${formatMessage( - strings.incidentDescriptionDrugsDrivingPrefixAutofill, - )} `) + - formatMessage( - strings.incidentDescriptionPrescriptionDrugsDrivingAutofill, - ) - break - } - return acc - }, '') - - const relevantSubstances = getRelevantSubstances( - deprecatedOffenses, - substances, - ) - - reason += relevantSubstances.reduce((acc, substance, index) => { - if (index === 0) { - acc += ` (${formatMessage( - strings.incidentDescriptionSubstancesPrefixAutofill, - )} ` - } else if (index === relevantSubstances.length - 1) { - acc += ' og ' - } else { - acc += ', ' - } - acc += formatMessage(substanceStrings[substance[0] as Substance], { - amount: substance[1], - }) - if (index === relevantSubstances.length - 1) { - acc += ')' - } - return acc - }, '') - - return reason -} - export const getLegalArguments = ( lawsBroken: number[][], formatMessage: IntlShape['formatMessage'], @@ -281,7 +216,7 @@ export const getLegalArguments = ( } } - // handle the subarticle of the first laws tuple + // handle the sub-article of the first laws tuple let articles = lawsBroken[0][1] === 0 ? '' : `${lawsBroken[0][1]}.` for (let i = 1; i < lawsBroken.length; i++) { @@ -305,90 +240,6 @@ export const getLegalArguments = ( }) } -export const getIncidentDescription = ( - indictmentCount: TIndictmentCount, - formatMessage: IntlShape['formatMessage'], - crimeScene?: CrimeScene, - subtypesRecord?: Record, -) => { - const { - deprecatedOffenses, - substances, - vehicleRegistrationNumber, - indictmentCountSubtypes, - policeCaseNumber, - } = indictmentCount - - const incidentLocation = crimeScene?.place || '[Vettvangur]' - const incidentDate = crimeScene?.date - ? formatDate(crimeScene.date, 'PPPP')?.replace('dagur,', 'daginn') || '' - : '[Dagsetning]' - - const vehicleRegistration = - vehicleRegistrationNumber || '[Skráningarnúmer ökutækis]' - - const subtypes = - (subtypesRecord && policeCaseNumber && subtypesRecord[policeCaseNumber]) || - [] - - if ( - subtypes.length === 0 || - (subtypes.length > 1 && - (!indictmentCountSubtypes?.length || - indictmentCountSubtypes.length === 0)) - ) { - return '' - } - - if ( - isTrafficViolationIndictmentCount(policeCaseNumber, subtypesRecord) || - indictmentCountSubtypes?.includes(IndictmentSubtype.TRAFFIC_VIOLATION) - ) { - if (!deprecatedOffenses || deprecatedOffenses.length === 0) { - return '' - } - - const reason = getIncidentDescriptionReason( - deprecatedOffenses, - substances || {}, - formatMessage, - ) - - const isSpeeding = indictmentCount.deprecatedOffenses?.includes( - IndictmentCountOffense.SPEEDING, - ) - - const recordedSpeed = indictmentCount.recordedSpeed ?? '[Mældur hraði]' - const speedLimit = indictmentCount.speedLimit ?? '[Leyfilegur hraði]' - - return formatMessage(strings.incidentDescriptionAutofill, { - incidentDate, - vehicleRegistrationNumber: vehicleRegistration, - reason, - incidentLocation, - isSpeeding, - recordedSpeed, - speedLimit, - }) - } - - if (subtypes.length === 1) { - return formatMessage(strings.indictmentDescriptionSubtypesAutofill, { - subtypes: indictmentSubtypes[subtypes[0]], - date: incidentDate, - }) - } - - const allSubtypes = indictmentCountSubtypes - ?.map((subtype) => indictmentSubtypes[subtype]) - .join(', ') - - return formatMessage(strings.indictmentDescriptionSubtypesAutofill, { - subtypes: allSubtypes, - date: incidentDate, - }) -} - export const IndictmentCount: FC = ({ indictmentCount, workingCase, @@ -397,8 +248,12 @@ export const IndictmentCount: FC = ({ updateIndictmentCountState, setWorkingCase, }) => { + const { features } = useContext(FeatureContext) + const isOffenseEndpointEnabled = features.includes(Feature.OFFENSE_ENDPOINTS) + const { formatMessage } = useIntl() const { lawTag } = useIndictmentCounts() + const { deleteOffense } = useOffenses() const [ vehicleRegistrationNumberErrorMessage, @@ -444,14 +299,30 @@ export const IndictmentCount: FC = ({ [lawTag, indictmentCount.lawsBroken], ) - const handleIndictmentCountChanges = (update: UpdateIndictmentCount) => { + const handleIndictmentCountChanges = ( + update: UpdateIndictmentCount, + updatedOffenses?: Offense[], + ) => { let lawsBroken - if (update.substances || update.deprecatedOffenses) { - lawsBroken = getLawsBroken( - update.deprecatedOffenses || indictmentCount.deprecatedOffenses, - update.substances || indictmentCount.substances, - ) + const hasUpdatedOffenses = isOffenseEndpointEnabled + ? updatedOffenses + : update.deprecatedOffenses + const hasUpdatedDeprecatedSubstances = isOffenseEndpointEnabled + ? false + : update.substances + if (hasUpdatedOffenses || hasUpdatedDeprecatedSubstances) { + const offenses = isOffenseEndpointEnabled + ? updatedOffenses?.map((o) => o.offense) + : update.deprecatedOffenses || indictmentCount.deprecatedOffenses + const substances = isOffenseEndpointEnabled + ? updatedOffenses?.reduce( + (res, offense) => Object.assign(res, offense.substances), + {} as SubstanceMap, + ) + : update.substances || indictmentCount.substances + + lawsBroken = getLawsBroken(offenses, substances) } if (lawsBroken !== undefined) { @@ -466,20 +337,26 @@ export const IndictmentCount: FC = ({ ? workingCase.crimeScenes[policeCaseNumber] : undefined - const incidentDescription = getIncidentDescription( - { + const incidentDescription = getIncidentDescription({ + indictmentCount: { ...indictmentCount, ...update, + ...(updatedOffenses ? { offenses: updatedOffenses } : {}), }, formatMessage, crimeScene, - workingCase.indictmentSubtypes, - ) - - onChange(indictmentCount.id, { - incidentDescription, - ...update, + subtypesRecord: workingCase.indictmentSubtypes, + isOffenseEndpointEnabled, }) + + onChange( + indictmentCount.id, + { + incidentDescription, + ...update, + }, + updatedOffenses, + ) } const handleSubtypeChange = ( @@ -492,14 +369,31 @@ export const IndictmentCount: FC = ({ checked ? currentSubtypes.add(subtype) : currentSubtypes.delete(subtype) - handleIndictmentCountChanges({ - indictmentCountSubtypes: Array.from(currentSubtypes), - ...(!currentSubtypes.has(IndictmentSubtype.TRAFFIC_VIOLATION) && { - deprecatedOffenses: [], - substances: {}, - vehicleRegistrationNumber: null, - }), - }) + const hasTrafficViolationSubType = currentSubtypes.has( + IndictmentSubtype.TRAFFIC_VIOLATION, + ) + if (!hasTrafficViolationSubType) { + indictmentCount.offenses?.forEach( + async (o) => + await deleteOffense(workingCase.id, indictmentCount.id, o.id), + ) + const updatedOffenses: Offense[] = [] + handleIndictmentCountChanges( + { + indictmentCountSubtypes: Array.from(currentSubtypes), + deprecatedOffenses: [], + substances: {}, + vehicleRegistrationNumber: null, + recordedSpeed: null, + speedLimit: null, + }, + updatedOffenses, + ) + } else { + handleIndictmentCountChanges({ + indictmentCountSubtypes: Array.from(currentSubtypes), + }) + } } const shouldShowTrafficViolationFields = () => { @@ -667,260 +561,277 @@ export const IndictmentCount: FC = ({ required /> - - - { + const selectedOffense = so?.value as IndictmentCountOffense + const deprecatedOffenses = [ + ...(indictmentCount.deprecatedOffenses ?? []), + selectedOffense, + ].sort(offensesCompare) + + handleIndictmentCountChanges({ + deprecatedOffenses, + }) + }} + value={null} + required + /> + + {indictmentCount.deprecatedOffenses && + indictmentCount.deprecatedOffenses.length > 0 && ( + + {indictmentCount.deprecatedOffenses.map((offense) => ( + + { + const deprecatedOffenses = ( + indictmentCount.deprecatedOffenses ?? [] + ).filter((o) => o !== offense) + + offenseSubstances[offense].forEach((e) => { + if (indictmentCount.substances) { + delete indictmentCount.substances[e] + } + }) + + handleIndictmentCountChanges({ + deprecatedOffenses, + substances: indictmentCount.substances, + ...(offense === + IndictmentCountOffense.SPEEDING && { + recordedSpeed: null, + speedLimit: null, + }), + }) + }} + > + + {formatMessage(enumStrings[offense])} + + + + + ))} + + )} + {indictmentCount.deprecatedOffenses?.includes( + IndictmentCountOffense.DRUNK_DRIVING, + ) && ( + + + { + removeErrorMessageIfValid( + ['empty'], + event.target.value, + bloodAlcoholContentErrorMessage, + setBloodAlcoholContentErrorMessage, + ) + + updateIndictmentCountState( + indictmentCount.id, + { + substances: { + ...indictmentCount.substances, + ALCOHOL: event.target.value, + }, + }, + setWorkingCase, + ) + }} + onBlur={(event) => { + const value = + event.target.value.length > 0 + ? `${event.target.value}${'0,00'.slice( + event.target.value.length, + )}` + : event.target.value + + validateAndSetErrorMessage( + ['empty'], + value, + setBloodAlcoholContentErrorMessage, + ) + + const substances = { + ...indictmentCount.substances, + ALCOHOL: value, + } + + handleIndictmentCountChanges({ + substances, + }) + }} > - { - const deprecatedOffenses = ( - indictmentCount.deprecatedOffenses ?? [] - ).filter((o) => o !== offense) - - offenseSubstances[offense].forEach((e) => { - if (indictmentCount.substances) { - delete indictmentCount.substances[e] - } - }) + + + + )} + {indictmentCount.deprecatedOffenses?.includes( + IndictmentCountOffense.SPEEDING, + ) && ( + + + + { + const recordedSpeed = parseInt(event.target.value) + + removeErrorMessageIfValid( + ['empty'], + event.target.value, + recordedSpeedErrorMessage, + setRecordedSpeedErrorMessage, + ) + + updateIndictmentCountState( + indictmentCount.id, + { recordedSpeed }, + setWorkingCase, + ) + }} + onBlur={(event) => { + const recordedSpeed = parseInt(event.target.value) + + if (Number.isNaN(recordedSpeed)) { + setRecordedSpeedErrorMessage( + 'Reitur má ekki vera tómur', + ) + return + } handleIndictmentCountChanges({ - deprecatedOffenses, - substances: indictmentCount.substances, - ...(offense === IndictmentCountOffense.SPEEDING && { - recordedSpeed: null, - speedLimit: null, - }), + recordedSpeed, }) }} > - - {formatMessage(enumStrings[offense])} - - - + + - ))} - - )} - {indictmentCount.deprecatedOffenses?.includes( - IndictmentCountOffense.DRUNK_DRIVING, - ) && ( - - - { - removeErrorMessageIfValid( - ['empty'], - event.target.value, - bloodAlcoholContentErrorMessage, - setBloodAlcoholContentErrorMessage, - ) - - updateIndictmentCountState( - indictmentCount.id, - { - substances: { - ...indictmentCount.substances, - ALCOHOL: event.target.value, - }, - }, - setWorkingCase, - ) - }} - onBlur={(event) => { - const value = - event.target.value.length > 0 - ? `${event.target.value}${'0,00'.slice( - event.target.value.length, - )}` - : event.target.value - - validateAndSetErrorMessage( - ['empty'], - value, - setBloodAlcoholContentErrorMessage, - ) - - const substances = { - ...indictmentCount.substances, - ALCOHOL: value, - } - - handleIndictmentCountChanges({ - substances, - }) - }} - > - - - - )} - {indictmentCount.deprecatedOffenses?.includes( - IndictmentCountOffense.SPEEDING, - ) && ( - - - - { - const recordedSpeed = parseInt(event.target.value) - - removeErrorMessageIfValid( - ['empty'], - event.target.value, - recordedSpeedErrorMessage, - setRecordedSpeedErrorMessage, - ) - - updateIndictmentCountState( - indictmentCount.id, - { recordedSpeed }, - setWorkingCase, - ) - }} - onBlur={(event) => { - const recordedSpeed = parseInt(event.target.value) + { + const speedLimit = parseInt(event.target.value) + + removeErrorMessageIfValid( + ['empty'], + event.target.value, + speedLimitErrorMessage, + setSpeedLimitErrorMessage, + ) + + updateIndictmentCountState( + indictmentCount.id, + { speedLimit }, + setWorkingCase, + ) + }} + onBlur={(event) => { + const speedLimit = parseInt(event.target.value) - if (Number.isNaN(recordedSpeed)) { - setRecordedSpeedErrorMessage('Reitur má ekki vera tómur') - return - } + if (Number.isNaN(speedLimit)) { + setSpeedLimitErrorMessage('Reitur má ekki vera tómur') + return + } - handleIndictmentCountChanges({ - recordedSpeed, - }) - }} - > - - - - { - const speedLimit = parseInt(event.target.value) - - removeErrorMessageIfValid( - ['empty'], - event.target.value, - speedLimitErrorMessage, - setSpeedLimitErrorMessage, - ) - - updateIndictmentCountState( - indictmentCount.id, - { speedLimit }, - setWorkingCase, - ) - }} - onBlur={(event) => { - const speedLimit = parseInt(event.target.value) - - if (Number.isNaN(speedLimit)) { - setSpeedLimitErrorMessage('Reitur má ekki vera tómur') - return - } - - handleIndictmentCountChanges({ - speedLimit, - }) - }} - > - - - + handleIndictmentCountChanges({ + speedLimit, + }) + }} + > + + + + )} + + {indictmentCount.deprecatedOffenses + ?.filter( + (offenseType) => + offenseType === + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING || + offenseType === + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ) + .map((offenseType) => ( + + + + ))} + )} - {indictmentCount.deprecatedOffenses - ?.filter( - (offenseType) => - offenseType === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING || - offenseType === - IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, - ) - .map((offenseType) => ( - - - - ))} { + if (!(o1.created && o2.created)) { + return 0 + } + if (o1.created < o2.created) { + return -1 + } + if (o1.created > o2.created) { + return 1 + } + return 0 +} + +const getUpdatedOffenses = ( + currentOffenses: Offense[], + updatedOffense: Offense, +) => + [ + ...currentOffenses.filter((o) => o.id !== updatedOffense.id), + updatedOffense, + ].sort(sortByCreatedDate) + +const getDrunkDriving = (offenses: Offense[]) => + offenses.find((o) => o.offense === IndictmentCountOffense.DRUNK_DRIVING) + +const getSpeeding = (offenses: Offense[]) => + offenses.find((o) => o.offense === IndictmentCountOffense.SPEEDING) + +const getDrugsDriving = (offenses: Offense[]) => + offenses?.filter( + (o) => + o.offense === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING || + o.offense === IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ) + +export const Offenses = ({ + workingCase, + setWorkingCase, + indictmentCount, + handleIndictmentCountChanges, + updateIndictmentCountState, +}: { + workingCase: Case + setWorkingCase: Dispatch> + indictmentCount: TempIndictmentCount + handleIndictmentCountChanges: ( + update: UpdateIndictmentCount, + updatedOffenses?: Offense[], + ) => void + updateIndictmentCountState: ( + indictmentCountId: string, + update: UpdateIndictmentCount, + setWorkingCase: Dispatch>, + updatedOffenses?: Offense[], + ) => void +}) => { + const { formatMessage } = useIntl() + const [bloodAlcoholContentErrorMessage, setBloodAlcoholContentErrorMessage] = + useState('') + + const { createOffense, updateOffense, deleteOffense } = useOffenses() + + const offenses = useMemo( + () => indictmentCount.offenses ?? [], + [indictmentCount.offenses], + ) + const drunkDrivingOffense = getDrunkDriving(offenses) + const speedingOffense = getSpeeding(offenses) + + const offensesOptions = useMemo( + () => + Object.values(IndictmentCountOffense).map((offense) => ({ + value: offense, + label: formatMessage(enumStrings[offense]), + disabled: offenses.some((o) => o.offense === offense), + })), + [formatMessage, offenses], + ) + + // handlers + const handleCreateOffense = async ( + selectedOffense: IndictmentCountOffense, + ) => { + const hasOffense = offenses.some((o) => o.offense === selectedOffense) + if (!hasOffense) { + const newOffense = await createOffense( + workingCase.id, + indictmentCount.id, + selectedOffense, + ) + if (newOffense) { + const updatedOffenses = [...offenses, newOffense] + + // handle changes on indictment count impacted by creating an offense + handleIndictmentCountChanges({}, updatedOffenses) + } + } + } + + const handleDeleteOffense = async ( + offenseId: string, + offense: IndictmentCountOffense, + ) => { + await deleteOffense(workingCase.id, indictmentCount.id, offenseId) + const updatedOffenses = offenses.filter((o) => o.id !== offenseId) + + // handle changes on indictment count impacted by deleting an offense + handleIndictmentCountChanges( + { + ...(offense === IndictmentCountOffense.SPEEDING && { + recordedSpeed: null, + speedLimit: null, + }), + }, + updatedOffenses, + ) + } + + const handleOffenseSubstanceUpdate = async ( + offense: Offense, + updatedSubstances: SubstanceMap, + ) => { + const offenseToBeUpdated = { + substances: { + ...offense.substances, + ...updatedSubstances, + } as SubstanceMap, + } + const updatedOffense = await updateOffense( + workingCase.id, + indictmentCount.id, + offense.id, + offenseToBeUpdated, + ) + + if (updatedOffense) { + const updatedOffenses = getUpdatedOffenses(offenses, updatedOffense) + handleIndictmentCountChanges({}, updatedOffenses) + } + } + + const handleAlcoholSubstanceUpdate = async ( + offense: Offense, + alcoholValue: string, + ) => { + const substances = { + ...offense.substances, + ALCOHOL: alcoholValue, + } as SubstanceMap + await handleOffenseSubstanceUpdate(offense, substances) + } + + return ( + <> + {/* OFFENSE */} + + + + + + )} + {/* SPEEDING OFFENSE FIELDS */} + {speedingOffense && ( + + )} + {/* DRUG SUBSTANCE FIELDS */} + {getDrugsDriving(offenses).map((o) => ( + + + + ))} + + ) +} diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Offenses/SpeedingOffenseFields.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Offenses/SpeedingOffenseFields.tsx new file mode 100644 index 000000000000..2bb53984cb43 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Offenses/SpeedingOffenseFields.tsx @@ -0,0 +1,139 @@ +import { Dispatch, SetStateAction, useState } from 'react' +import InputMask from 'react-input-mask' +import { useIntl } from 'react-intl' + +import { Box, Input } from '@island.is/island-ui/core' +import { SectionHeading } from '@island.is/judicial-system-web/src/components' +import { + Case, + Offense, +} from '@island.is/judicial-system-web/src/graphql/schema' +import { TempIndictmentCount } from '@island.is/judicial-system-web/src/types' +import { removeErrorMessageIfValid } from '@island.is/judicial-system-web/src/utils/formHelper' +import { UpdateIndictmentCount } from '@island.is/judicial-system-web/src/utils/hooks' + +import { indictmentCount as strings } from '../IndictmentCount.strings' + +export const SpeedingOffenseFields = ({ + setWorkingCase, + indictmentCount, + handleIndictmentCountChanges, + updateIndictmentCountState, +}: { + setWorkingCase: Dispatch> + indictmentCount: TempIndictmentCount + handleIndictmentCountChanges: ( + update: UpdateIndictmentCount, + updatedOffenses?: Offense[], + ) => void + updateIndictmentCountState: ( + indictmentCountId: string, + update: UpdateIndictmentCount, + setWorkingCase: Dispatch>, + updatedOffenses?: Offense[], + ) => void +}) => { + const { formatMessage } = useIntl() + + const [recordedSpeedErrorMessage, setRecordedSpeedErrorMessage] = + useState('') + const [speedLimitErrorMessage, setSpeedLimitErrorMessage] = + useState('') + return ( + + + + { + const recordedSpeed = parseInt(event.target.value) + + removeErrorMessageIfValid( + ['empty'], + event.target.value, + recordedSpeedErrorMessage, + setRecordedSpeedErrorMessage, + ) + + updateIndictmentCountState( + indictmentCount.id, + { recordedSpeed }, + setWorkingCase, + ) + }} + onBlur={(event) => { + const recordedSpeed = parseInt(event.target.value) + + if (Number.isNaN(recordedSpeed)) { + setRecordedSpeedErrorMessage('Reitur má ekki vera tómur') + return + } + + handleIndictmentCountChanges({ + recordedSpeed, + }) + }} + > + + + + { + const speedLimit = parseInt(event.target.value) + + removeErrorMessageIfValid( + ['empty'], + event.target.value, + speedLimitErrorMessage, + setSpeedLimitErrorMessage, + ) + + updateIndictmentCountState( + indictmentCount.id, + { speedLimit }, + setWorkingCase, + ) + }} + onBlur={(event) => { + const speedLimit = parseInt(event.target.value) + + if (Number.isNaN(speedLimit)) { + setSpeedLimitErrorMessage('Reitur má ekki vera tómur') + return + } + + handleIndictmentCountChanges({ + speedLimit, + }) + }} + > + + + + ) +} diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/DeprecatedSubstances.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/DeprecatedSubstances.tsx new file mode 100644 index 000000000000..559c5a88df22 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Substances/DeprecatedSubstances.tsx @@ -0,0 +1,127 @@ +import { FC, useMemo } from 'react' +import { useIntl } from 'react-intl' + +import { Box, Select } from '@island.is/island-ui/core' +import { + offenseSubstances, + Substance as SubstanceEnum, +} from '@island.is/judicial-system/types' +import { SectionHeading } from '@island.is/judicial-system-web/src/components' +import { IndictmentCountOffense } from '@island.is/judicial-system-web/src/graphql/schema' +import { + ReactSelectOption, + TempIndictmentCount as TIndictmentCount, +} from '@island.is/judicial-system-web/src/types' +import { UpdateIndictmentCount } from '@island.is/judicial-system-web/src/utils/hooks' + +import { Substance } from '../Substance/Substance' +import { substances as strings } from './Substances.strings' +import { substanceEnum } from './SubstancesEnum.strings' +import * as styles from './Substances.css' + +interface Props { + indictmentCount: TIndictmentCount + indictmentCountOffenseType: IndictmentCountOffense + onChange: (updatedIndictmentCount: UpdateIndictmentCount) => void +} + +export const DeprecatedSubstances: FC = ({ + indictmentCount, + indictmentCountOffenseType, + onChange, +}) => { + const { formatMessage } = useIntl() + + const getSubstanceOptions = useMemo( + () => + Object.values(offenseSubstances[indictmentCountOffenseType]).map( + (sub) => ({ + value: sub, + label: formatMessage(substanceEnum[sub]), + disabled: indictmentCount.substances + ? indictmentCount.substances[sub] !== undefined + : false, + }), + ), + [formatMessage, indictmentCount.substances, indictmentCountOffenseType], + ) + + const handleUpdateSubstanceAmount = ( + substanceId: SubstanceEnum, + substanceAmount: string, + ) => { + const substances = { + ...indictmentCount.substances, + [substanceId]: substanceAmount, + } + onChange({ substances }) + } + + const handleDeleteSubstance = (substanceId: SubstanceEnum) => { + if (indictmentCount.substances) { + delete indictmentCount.substances[substanceId] + } + onChange({ substances: indictmentCount.substances }) + } + + return ( + + + + { const substance = (selectedOption as ReactSelectOption).value - const substances = { - ...indictmentCount.substances, + const updatedSubstances = { + ...substances, [substance]: '', } - - onChange({ substances }) + onChange(offense, updatedSubstances) }} value={null} required /> - {indictmentCount.substances && ( + {substances && (
- {(Object.keys(indictmentCount.substances) as SubstanceEnum[]) - .filter((s) => - offenseSubstances[indictmentCountOffenseType].includes(s), - ) + {(Object.keys(substances) as SubstanceEnum[]) + .filter((s) => offenseSubstances[offenseType].includes(s)) .map((substance) => (
{ diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.spec.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.spec.ts new file mode 100644 index 000000000000..c58428911691 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.spec.ts @@ -0,0 +1,120 @@ +import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { formatMessage } from '@island.is/judicial-system-web/src/utils/testHelpers' + +import { getDeprecatedIncidentDescriptionReason } from './getDeprecatedIncidentDescriptionReason' + +describe('getDeprecatedIncidentDescriptionReason', () => { + test('should return a description for one offense', () => { + const offenses = [IndictmentCountOffense.DRIVING_WITHOUT_LICENCE] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe('sviptur ökurétti') + }) + + test('should return a description for two offense', () => { + const offenses = [ + IndictmentCountOffense.DRIVING_WITHOUT_LICENCE, + IndictmentCountOffense.DRUNK_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe('sviptur ökurétti og undir áhrifum áfengis') + }) + + test('should return a description with prescription drugs', () => { + const offenses = [ + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa slævandi lyfja', + ) + }) + + test('should return a description with illegal drugs', () => { + const offenses = [ + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', + ) + }) + + test('should return a description with illegal drugs as third offense', () => { + const offenses = [ + IndictmentCountOffense.DRIVING_WITHOUT_LICENCE, + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe( + 'sviptur ökurétti, undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', + ) + }) + + test('should return a description with illegal and prescription drugs', () => { + const offenses = [ + IndictmentCountOffense.DRUNK_DRIVING, + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', + ) + }) + + test('should return a description with only illegal and prescription drugs', () => { + const offenses = [ + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + ] + + const result = getDeprecatedIncidentDescriptionReason( + offenses, + {}, + formatMessage, + ) + + expect(result).toBe( + 'óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', + ) + }) +}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.ts new file mode 100644 index 000000000000..bc5da0ef8d0e --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getDeprecatedIncidentDescriptionReason.ts @@ -0,0 +1,90 @@ +import { IntlShape } from 'react-intl' + +import { + IndictmentCountOffense, + Substance, + SubstanceMap, +} from '@island.is/judicial-system/types' + +import { getRelevantSubstances } from '../IndictmentCount' +import { indictmentCount as strings } from '../IndictmentCount.strings' +import { indictmentCountSubstanceEnum as substanceStrings } from '../IndictmentCountSubstanceEnum.strings' + +export const getDeprecatedIncidentDescriptionReason = ( + deprecatedOffenses: IndictmentCountOffense[], + substances: SubstanceMap, + formatMessage: IntlShape['formatMessage'], +) => { + let reason = deprecatedOffenses + .filter((offense) => offense !== IndictmentCountOffense.SPEEDING) + .reduce((acc, offense, index) => { + if ( + (deprecatedOffenses.length > 1 && + index === deprecatedOffenses.length - 1) || + (deprecatedOffenses.length > 2 && + index === deprecatedOffenses.length - 2 && + offense === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING) + ) { + acc += ' og ' + } else if (index > 0) { + acc += ', ' + } + switch (offense) { + case IndictmentCountOffense.DRIVING_WITHOUT_LICENCE: + acc += formatMessage( + strings.incidentDescriptionDrivingWithoutLicenceAutofill, + ) + break + case IndictmentCountOffense.DRUNK_DRIVING: + acc += formatMessage(strings.incidentDescriptionDrunkDrivingAutofill) + break + case IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING: + acc += `${formatMessage( + strings.incidentDescriptionDrugsDrivingPrefixAutofill, + )} ${formatMessage( + strings.incidentDescriptionIllegalDrugsDrivingAutofill, + )}` + break + case IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING: + acc += + (deprecatedOffenses.includes( + IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + ) + ? '' + : `${formatMessage( + strings.incidentDescriptionDrugsDrivingPrefixAutofill, + )} `) + + formatMessage( + strings.incidentDescriptionPrescriptionDrugsDrivingAutofill, + ) + break + } + return acc + }, '') + + const relevantSubstances = getRelevantSubstances( + deprecatedOffenses, + substances, + ) + + reason += relevantSubstances.reduce((acc, substance, index) => { + if (index === 0) { + acc += ` (${formatMessage( + strings.incidentDescriptionSubstancesPrefixAutofill, + )} ` + } else if (index === relevantSubstances.length - 1) { + acc += ' og ' + } else { + acc += ', ' + } + acc += formatMessage(substanceStrings[substance[0] as Substance], { + amount: substance[1], + }) + if (index === relevantSubstances.length - 1) { + acc += ')' + } + return acc + }, '') + + return reason +} diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDecriptionReason.spec.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDecriptionReason.spec.ts new file mode 100644 index 000000000000..6684a6740cd7 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDecriptionReason.spec.ts @@ -0,0 +1,125 @@ +import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { Offense } from '@island.is/judicial-system-web/src/graphql/schema' +import { formatMessage } from '@island.is/judicial-system-web/src/utils/testHelpers' + +import { getIncidentDescriptionReason } from './getIncidentDescriptionReason' + +describe('getIncidentDescriptionReason', () => { + test('should return a description for one offense', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRIVING_WITHOUT_LICENCE, + substances: {}, + }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe('sviptur ökurétti') + }) + + test('should return a description for two offense', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRIVING_WITHOUT_LICENCE, + substances: {}, + }, + { offense: IndictmentCountOffense.DRUNK_DRIVING, substances: {} }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe('sviptur ökurétti og undir áhrifum áfengis') + }) + + test('should return a description with prescription drugs', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRUNK_DRIVING, + substances: {}, + }, + { + offense: IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + substances: {}, + }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa slævandi lyfja', + ) + }) + + test('should return a description with illegal drugs', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRUNK_DRIVING, + substances: {}, + }, + { offense: IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, substances: {} }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', + ) + }) + + test('should return a description with illegal drugs as third offense', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRIVING_WITHOUT_LICENCE, + substances: {}, + }, + { offense: IndictmentCountOffense.DRUNK_DRIVING, substances: {} }, + { offense: IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, substances: {} }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe( + 'sviptur ökurétti, undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna', + ) + }) + + test('should return a description with illegal and prescription drugs', () => { + const offenses = [ + { + offense: IndictmentCountOffense.DRUNK_DRIVING, + substances: {}, + }, + { offense: IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, substances: {} }, + { + offense: IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + substances: {}, + }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe( + 'undir áhrifum áfengis og óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', + ) + }) + + test('should return a description with only illegal and prescription drugs', () => { + const offenses = [ + { + offense: IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + substances: {}, + }, + { + offense: IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING, + substances: {}, + }, + ] as Offense[] + + const result = getIncidentDescriptionReason(offenses, formatMessage) + + expect(result).toBe( + 'óhæfur til að stjórna henni örugglega vegna áhrifa ávana- og fíkniefna og slævandi lyfja', + ) + }) +}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.spec.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.spec.ts new file mode 100644 index 000000000000..78a39dcd1472 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.spec.ts @@ -0,0 +1,155 @@ +import { IndictmentCountOffense } from '@island.is/judicial-system/types' +import { IndictmentSubtype } from '@island.is/judicial-system/types' +import { Offense } from '@island.is/judicial-system-web/src/graphql/schema' +import { formatMessage } from '@island.is/judicial-system-web/src/utils/testHelpers' + +import { getIncidentDescription } from './getIncidentDescription' + +const IS_OFFENSE_ENDPOINT_ENABLED = true + +describe('getIncidentDescription', () => { + test('should return an empty string if there are no offenses in traffic violations', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + offenses: [], + policeCaseNumber: '123-123-123', + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, + isOffenseEndpointEnabled: IS_OFFENSE_ENDPOINT_ENABLED, + }) + + expect(result).toBe('') + }) + + test('should return an empty string if deprecatedOffenses are missing in traffic violations', () => { + const result = getIncidentDescription({ + indictmentCount: { id: 'testId', policeCaseNumber: '123-123-123' }, + formatMessage, + crimeScene: {}, + subtypesRecord: { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, + }) + + expect(result).toBe('') + }) + + test('should return a description for only traffic violations', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + offenses: [ + { offense: IndictmentCountOffense.DRUNK_DRIVING }, + ] as Offense[], + policeCaseNumber: '123-123-123', + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { '123-123-123': [IndictmentSubtype.TRAFFIC_VIOLATION] }, + isOffenseEndpointEnabled: IS_OFFENSE_ENDPOINT_ENABLED, + }) + + expect(result).toBe( + 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', + ) + }) + + test('should return a description for a single subtype that is not a traffic violation', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + policeCaseNumber: '123-123-123', + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { '123-123-123': [IndictmentSubtype.CUSTOMS_VIOLATION] }, + }) + + expect(result).toBe('fyrir [tollalagabrot] með því að hafa, [Dagsetning]') + }) + + test('should return a description when there are multiple subtypes but only traffic violation is selected', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + policeCaseNumber: '123-123-123', + offenses: [ + { offense: IndictmentCountOffense.DRUNK_DRIVING }, + ] as Offense[], + indictmentCountSubtypes: [IndictmentSubtype.TRAFFIC_VIOLATION], + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { + '123-123-123': [ + IndictmentSubtype.CUSTOMS_VIOLATION, + IndictmentSubtype.TRAFFIC_VIOLATION, + ], + }, + isOffenseEndpointEnabled: IS_OFFENSE_ENDPOINT_ENABLED, + }) + + expect(result).toBe( + 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', + ) + }) + + test('should return a description when there are multiple subtypes and all are selected', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + policeCaseNumber: '123-123-123', + offenses: [ + { offense: IndictmentCountOffense.DRUNK_DRIVING }, + ] as Offense[], + indictmentCountSubtypes: [ + IndictmentSubtype.CUSTOMS_VIOLATION, + IndictmentSubtype.THEFT, + ], + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { + '123-123-123': [ + IndictmentSubtype.CUSTOMS_VIOLATION, + IndictmentSubtype.THEFT, + ], + }, + isOffenseEndpointEnabled: IS_OFFENSE_ENDPOINT_ENABLED, + }) + + expect(result).toBe( + 'fyrir [tollalagabrot, þjófnaður] með því að hafa, [Dagsetning]', + ) + }) + + test('should return the traffic violation description when there are multiple subtypes, all are selected and one is a traffic violation', () => { + const result = getIncidentDescription({ + indictmentCount: { + id: 'testId', + policeCaseNumber: '123-123-123', + offenses: [ + { offense: IndictmentCountOffense.DRUNK_DRIVING }, + ] as Offense[], + indictmentCountSubtypes: [ + IndictmentSubtype.CUSTOMS_VIOLATION, + IndictmentSubtype.TRAFFIC_VIOLATION, + ], + }, + formatMessage, + crimeScene: {}, + subtypesRecord: { + '123-123-123': [ + IndictmentSubtype.CUSTOMS_VIOLATION, + IndictmentSubtype.TRAFFIC_VIOLATION, + ], + }, + isOffenseEndpointEnabled: IS_OFFENSE_ENDPOINT_ENABLED, + }) + + expect(result).toBe( + 'fyrir umferðarlagabrot með því að hafa, [Dagsetning], ekið bifreiðinni [Skráningarnúmer ökutækis] undir áhrifum áfengis um [Vettvangur], þar sem lögregla stöðvaði aksturinn.', + ) + }) +}) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.ts new file mode 100644 index 000000000000..11d9a792c4a7 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescription.ts @@ -0,0 +1,154 @@ +import { IntlShape } from 'react-intl' + +import { + formatDate, + indictmentSubtypes, +} from '@island.is/judicial-system/formatters' +import { + CrimeScene, + IndictmentSubtype, + SubstanceMap, +} from '@island.is/judicial-system/types' +import { + IndictmentCountOffense, + Maybe, + Offense, +} from '@island.is/judicial-system-web/src/graphql/schema' +import { TempIndictmentCount } from '@island.is/judicial-system-web/src/types' +import { isTrafficViolationIndictmentCount } from '@island.is/judicial-system-web/src/utils/formHelper' + +import { getDeprecatedIncidentDescriptionReason } from './getDeprecatedIncidentDescriptionReason' +import { getIncidentDescriptionReason } from './getIncidentDescriptionReason' +import { indictmentCount as strings } from '../IndictmentCount.strings' + +const getDeprecatedIncidentDescriptionProps = ({ + deprecatedOffenses, + formatMessage, + substances, +}: { + deprecatedOffenses?: Maybe + formatMessage: IntlShape['formatMessage'] + substances?: SubstanceMap | null +}) => { + if (!deprecatedOffenses || deprecatedOffenses.length === 0) { + return undefined + } + + const reason = getDeprecatedIncidentDescriptionReason( + deprecatedOffenses, + substances || {}, + formatMessage, + ) + const isSpeeding = deprecatedOffenses?.includes( + IndictmentCountOffense.SPEEDING, + ) + + return { reason, isSpeeding } +} + +const getIncidentDescriptionProps = ({ + offenses, + formatMessage, +}: { + offenses?: Maybe + formatMessage: IntlShape['formatMessage'] +}) => { + if (!offenses || offenses.length === 0) { + return undefined + } + const reason = getIncidentDescriptionReason(offenses || [], formatMessage) + const isSpeeding = offenses?.some( + (o) => o.offense === IndictmentCountOffense.SPEEDING, + ) + return { reason, isSpeeding } +} + +export const getIncidentDescription = ({ + indictmentCount, + formatMessage, + crimeScene, + subtypesRecord, + isOffenseEndpointEnabled, +}: { + indictmentCount: TempIndictmentCount + formatMessage: IntlShape['formatMessage'] + crimeScene?: CrimeScene + subtypesRecord?: Record + isOffenseEndpointEnabled?: boolean +}) => { + const { + offenses, + deprecatedOffenses, + substances, + vehicleRegistrationNumber, + indictmentCountSubtypes, + policeCaseNumber, + } = indictmentCount + + const incidentLocation = crimeScene?.place || '[Vettvangur]' + const incidentDate = crimeScene?.date + ? formatDate(crimeScene.date, 'PPPP')?.replace('dagur,', 'daginn') || '' + : '[Dagsetning]' + + const vehicleRegistration = + vehicleRegistrationNumber || '[Skráningarnúmer ökutækis]' + + const subtypes = + (subtypesRecord && policeCaseNumber && subtypesRecord[policeCaseNumber]) || + [] + + if ( + subtypes.length === 0 || + (subtypes.length > 1 && + (!indictmentCountSubtypes?.length || + indictmentCountSubtypes.length === 0)) + ) { + return '' + } + + if ( + isTrafficViolationIndictmentCount(policeCaseNumber, subtypesRecord) || + indictmentCountSubtypes?.includes(IndictmentSubtype.TRAFFIC_VIOLATION) + ) { + const incidentDescriptionProps = isOffenseEndpointEnabled + ? getIncidentDescriptionProps({ offenses, formatMessage }) + : getDeprecatedIncidentDescriptionProps({ + deprecatedOffenses, + formatMessage, + substances, + }) + if (!incidentDescriptionProps) { + return '' + } + + const { reason, isSpeeding } = incidentDescriptionProps + const recordedSpeed = indictmentCount.recordedSpeed ?? '[Mældur hraði]' + const speedLimit = indictmentCount.speedLimit ?? '[Leyfilegur hraði]' + + return formatMessage(strings.incidentDescriptionAutofill, { + incidentDate, + vehicleRegistrationNumber: vehicleRegistration, + reason, + incidentLocation, + isSpeeding, + recordedSpeed, + speedLimit, + }) + } + + if (subtypes.length === 1) { + return formatMessage(strings.indictmentDescriptionSubtypesAutofill, { + subtypes: indictmentSubtypes[subtypes[0]], + date: incidentDate, + }) + } + + const allSubtypes = indictmentCountSubtypes + ?.map((subtype) => indictmentSubtypes[subtype]) + .join(', ') + + return formatMessage(strings.indictmentDescriptionSubtypesAutofill, { + subtypes: allSubtypes, + date: incidentDate, + }) +} diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescriptionReason.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescriptionReason.ts new file mode 100644 index 000000000000..2494f51d0ca6 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/lib/getIncidentDescriptionReason.ts @@ -0,0 +1,86 @@ +import { IntlShape } from 'react-intl' + +import { + IndictmentCountOffense, + Substance, + SubstanceMap, +} from '@island.is/judicial-system/types' +import { Offense } from '@island.is/judicial-system-web/src/graphql/schema' + +import { indictmentCount as strings } from '../IndictmentCount.strings' +import { indictmentCountSubstanceEnum as substanceStrings } from '../IndictmentCountSubstanceEnum.strings' + +export const getIncidentDescriptionReason = ( + offenses: Offense[], + formatMessage: IntlShape['formatMessage'], +) => { + let reason = offenses + .filter((o) => o.offense !== IndictmentCountOffense.SPEEDING) + .reduce((acc, o, index) => { + if ( + (offenses.length > 1 && index === offenses.length - 1) || + (offenses.length > 2 && + index === offenses.length - 2 && + o.offense === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING) + ) { + acc += ' og ' + } else if (index > 0) { + acc += ', ' + } + switch (o.offense) { + case IndictmentCountOffense.DRIVING_WITHOUT_LICENCE: + acc += formatMessage( + strings.incidentDescriptionDrivingWithoutLicenceAutofill, + ) + break + case IndictmentCountOffense.DRUNK_DRIVING: + acc += formatMessage(strings.incidentDescriptionDrunkDrivingAutofill) + break + case IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING: + acc += `${formatMessage( + strings.incidentDescriptionDrugsDrivingPrefixAutofill, + )} ${formatMessage( + strings.incidentDescriptionIllegalDrugsDrivingAutofill, + )}` + break + case IndictmentCountOffense.PRESCRIPTION_DRUGS_DRIVING: + acc += + (offenses.some( + (o) => o.offense === IndictmentCountOffense.ILLEGAL_DRUGS_DRIVING, + ) + ? '' + : `${formatMessage( + strings.incidentDescriptionDrugsDrivingPrefixAutofill, + )} `) + + formatMessage( + strings.incidentDescriptionPrescriptionDrugsDrivingAutofill, + ) + break + } + return acc + }, '') + + const substances = offenses + .filter((o) => o.substances) + .flatMap(({ substances }) => Object.entries(substances as SubstanceMap)) + reason += substances.reduce((acc, substance, index) => { + if (index === 0) { + acc += ` (${formatMessage( + strings.incidentDescriptionSubstancesPrefixAutofill, + )} ` + } else if (index === substances.length - 1) { + acc += ' og ' + } else { + acc += ', ' + } + acc += formatMessage(substanceStrings[substance[0] as Substance], { + amount: substance[1], + }) + if (index === substances.length - 1) { + acc += ')' + } + return acc + }, '') + + return reason +} diff --git a/apps/judicial-system/web/src/utils/formHelper.ts b/apps/judicial-system/web/src/utils/formHelper.ts index 5d1b73f97de5..03d2a238eecf 100644 --- a/apps/judicial-system/web/src/utils/formHelper.ts +++ b/apps/judicial-system/web/src/utils/formHelper.ts @@ -173,7 +173,7 @@ export type stepValidationsType = { [constants.INVESTIGATION_CASE_POLICE_CONFIRMATION_ROUTE]: () => boolean [constants.INDICTMENTS_CASE_FILE_ROUTE]: () => boolean [constants.INDICTMENTS_PROCESSING_ROUTE]: (theCase: Case) => boolean - [constants.INDICTMENTS_INDICTMENT_ROUTE]: (theCase: Case) => boolean + [constants.INDICTMENTS_INDICTMENT_ROUTE]: (theCase: Case, isOffenseEndpointEnabled?: boolean) => boolean [constants.INDICTMENTS_CASE_FILES_ROUTE]: (theCase: Case) => boolean [constants.RESTRICTION_CASE_RECEPTION_AND_ASSIGNMENT_ROUTE]: ( theCase: Case, @@ -255,8 +255,8 @@ export const stepValidations = (): stepValidationsType => { [constants.INDICTMENTS_CASE_FILE_ROUTE]: () => true, [constants.INDICTMENTS_PROCESSING_ROUTE]: (theCase: Case) => validations.isProcessingStepValidIndictments(theCase), - [constants.INDICTMENTS_INDICTMENT_ROUTE]: (theCase: Case) => - validations.isIndictmentStepValid(theCase), + [constants.INDICTMENTS_INDICTMENT_ROUTE]: (theCase: Case, isOffenseEndpointEnabled?: boolean) => + validations.isIndictmentStepValid(theCase, isOffenseEndpointEnabled), [constants.INDICTMENTS_CASE_FILES_ROUTE]: () => true, [constants.INDICTMENTS_SUMMARY_ROUTE]: () => true, [constants.RESTRICTION_CASE_RECEPTION_AND_ASSIGNMENT_ROUTE]: ( diff --git a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/index.ts b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/index.ts index 6f06420c38b8..7f990720ea2b 100644 --- a/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useIndictmentCounts/index.ts @@ -4,7 +4,10 @@ import { useIntl } from 'react-intl' import { toast } from '@island.is/island-ui/core' import { SubstanceMap } from '@island.is/judicial-system/types' import { errors } from '@island.is/judicial-system-web/messages' -import { UpdateIndictmentCountInput } from '@island.is/judicial-system-web/src/graphql/schema' +import { + Offense, + UpdateIndictmentCountInput, +} from '@island.is/judicial-system-web/src/graphql/schema' import { indictmentCount } from '@island.is/judicial-system-web/src/routes/Prosecutor/Indictments/Indictment/IndictmentCount.strings' import { TempCase as Case } from '@island.is/judicial-system-web/src/types' @@ -103,6 +106,7 @@ const useIndictmentCounts = () => { indictmentCountId: string, update: UpdateIndictmentCount, setWorkingCase: Dispatch>, + updatedOffenses?: Offense[], ) => { setWorkingCase((prevWorkingCase) => { if (!prevWorkingCase.indictmentCounts) { @@ -119,6 +123,7 @@ const useIndictmentCounts = () => { newIndictmentCounts[indictmentCountIndexToUpdate] = { ...newIndictmentCounts[indictmentCountIndexToUpdate], ...update, + ...(updatedOffenses ? { offenses: updatedOffenses } : {}), } return { ...prevWorkingCase, indictmentCounts: newIndictmentCounts } diff --git a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts index 17b76a2bf529..68de6a59e0db 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts @@ -1,3 +1,4 @@ +import { useContext } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' @@ -7,6 +8,7 @@ import { getAppealResultTextByValue, } from '@island.is/judicial-system/formatters' import { + Feature, isCompletedCase, isCourtOfAppealsUser, isDefenceUser, @@ -17,6 +19,7 @@ import { isRestrictionCase, } from '@island.is/judicial-system/types' import { core, sections } from '@island.is/judicial-system-web/messages' +import { FeatureContext } from '@island.is/judicial-system-web/src/components' import { RouteSection } from '@island.is/judicial-system-web/src/components/PageLayout/PageLayout' import { formatCaseResult } from '@island.is/judicial-system-web/src/components/PageLayout/utils' import { @@ -37,6 +40,7 @@ const validateFormStepper = ( isActiveSubSectionValid: boolean, steps: string[], workingCase: Case, + isOffenseEndpointEnabled?: boolean ) => { if (!isActiveSubSectionValid) { return false @@ -46,7 +50,7 @@ const validateFormStepper = ( return steps.some( (step) => - validationForStep[step as keyof typeof validationForStep](workingCase) === + validationForStep[step as keyof typeof validationForStep](workingCase, isOffenseEndpointEnabled) === false, ) ? false @@ -57,7 +61,11 @@ const useSections = ( isValid = true, onNavigationTo?: (destination: keyof stepValidationsType) => Promise, ) => { + const { features } = useContext(FeatureContext) + const isOffenseEndpointEnabled = features.includes(Feature.OFFENSE_ENDPOINTS) + const { formatMessage } = useIntl() + const router = useRouter() const isActive = (pathname: string) => router.pathname.replace(/\/\[\w+\]/g, '') === pathname @@ -514,6 +522,7 @@ const useSections = ( constants.INDICTMENTS_PROCESSING_ROUTE, ], workingCase, + isOffenseEndpointEnabled ) && onNavigationTo ? async () => diff --git a/apps/judicial-system/web/src/utils/validate.ts b/apps/judicial-system/web/src/utils/validate.ts index 766c76eb3df1..f4590e41433f 100644 --- a/apps/judicial-system/web/src/utils/validate.ts +++ b/apps/judicial-system/web/src/utils/validate.ts @@ -289,7 +289,7 @@ export const isProcessingStepValidIndictments = ( ) } -export const isIndictmentStepValid = (workingCase: Case): boolean => { +export const isIndictmentStepValid = (workingCase: Case, isOffenseEndpointEnabled?: boolean): boolean => { const hasValidDemands = Boolean( workingCase.demands && (!workingCase.hasCivilClaims || workingCase.civilDemands), @@ -299,20 +299,38 @@ export const isIndictmentStepValid = (workingCase: Case): boolean => { return false } - const isValidSpeedingIndictmentCount = (indictmentCount: IndictmentCount) => - indictmentCount.deprecatedOffenses?.includes( + const isValidSpeedingIndictmentCount = (indictmentCount: IndictmentCount) => { + if (indictmentCount.offenses) { + return indictmentCount.offenses.some( + (o) => o.offense === IndictmentCountOffense.SPEEDING, + ) + ? Boolean(indictmentCount.recordedSpeed) && + Boolean(indictmentCount.speedLimit) + : true + } + return indictmentCount.deprecatedOffenses?.includes( IndictmentCountOffense.SPEEDING, ) ? Boolean(indictmentCount.recordedSpeed) && - Boolean(indictmentCount.speedLimit) + Boolean(indictmentCount.speedLimit) : true + } - const isValidTrafficViolation = (indictmentCount: IndictmentCount) => - Boolean(indictmentCount.policeCaseNumber) && - Boolean( + const hasOffenses = (indictmentCount: IndictmentCount) => { + if (isOffenseEndpointEnabled) { + return Boolean( + indictmentCount.offenses && indictmentCount.offenses?.length > 0, + ) + } + return Boolean( indictmentCount.deprecatedOffenses && indictmentCount.deprecatedOffenses?.length > 0, - ) && + ) + } + + const isValidTrafficViolation = (indictmentCount: IndictmentCount) => + Boolean(indictmentCount.policeCaseNumber) && + hasOffenses(indictmentCount) && Boolean(indictmentCount.vehicleRegistrationNumber) && Boolean(indictmentCount.lawsBroken) && Boolean(indictmentCount.incidentDescription) && diff --git a/charts/judicial-system-services/judicial-system-api/values.dev.yaml b/charts/judicial-system-services/judicial-system-api/values.dev.yaml index 66cf641301b8..b2f66b567de8 100644 --- a/charts/judicial-system-services/judicial-system-api/values.dev.yaml +++ b/charts/judicial-system-services/judicial-system-api/values.dev.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'preview.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system-services/judicial-system-api/values.prod.yaml b/charts/judicial-system-services/judicial-system-api/values.prod.yaml index e928296685e0..4c07eaca2dc5 100644 --- a/charts/judicial-system-services/judicial-system-api/values.prod.yaml +++ b/charts/judicial-system-services/judicial-system-api/values.prod.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system-services/judicial-system-api/values.staging.yaml b/charts/judicial-system-services/judicial-system-api/values.staging.yaml index e0da3dd0d122..31320fb73289 100644 --- a/charts/judicial-system-services/judicial-system-api/values.staging.yaml +++ b/charts/judicial-system-services/judicial-system-api/values.staging.yaml @@ -30,7 +30,7 @@ env: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system/values.dev.yaml b/charts/judicial-system/values.dev.yaml index 1e74bbab6feb..b1644a2b839d 100644 --- a/charts/judicial-system/values.dev.yaml +++ b/charts/judicial-system/values.dev.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'preview.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.dev01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system/values.prod.yaml b/charts/judicial-system/values.prod.yaml index 03316a036d35..5170fe9ddbdf 100644 --- a/charts/judicial-system/values.prod.yaml +++ b/charts/judicial-system/values.prod.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'master' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://innskra.island.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/charts/judicial-system/values.staging.yaml b/charts/judicial-system/values.staging.yaml index 4ec11e1be050..d663fbdb6727 100644 --- a/charts/judicial-system/values.staging.yaml +++ b/charts/judicial-system/values.staging.yaml @@ -30,7 +30,7 @@ judicial-system-api: BACKEND_URL: 'http://web-judicial-system-backend' CONTENTFUL_ENVIRONMENT: 'test' CONTENTFUL_HOST: 'cdn.contentful.com' - HIDDEN_FEATURES: '' + HIDDEN_FEATURES: 'OFFENSE_ENDPOINTS' IDENTITY_SERVER_ISSUER_URL: 'https://identity-server.staging01.devland.is' LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' diff --git a/libs/judicial-system/types/src/lib/feature.ts b/libs/judicial-system/types/src/lib/feature.ts index 742e56c2f145..11d39d5aeb9e 100644 --- a/libs/judicial-system/types/src/lib/feature.ts +++ b/libs/judicial-system/types/src/lib/feature.ts @@ -1,3 +1,4 @@ export enum Feature { NONE = 'NONE', // must be at least one + OFFENSE_ENDPOINTS = 'OFFENSE_ENDPOINTS', }