From 4b82fdf21ef700b0fd5d5e85b061977088ba4865 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Tue, 28 May 2024 15:18:08 +0000 Subject: [PATCH 01/42] Add POSTPONED_TO_VERDICT radio button --- .../Conclusion/Conclusion.strings.ts | 18 ++++++ .../Indictments/Conclusion/Conclusion.tsx | 60 ++++++++++++++++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts index 754da4c18635..6a22a314e854 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts @@ -17,17 +17,35 @@ export const strings = defineMessages({ defaultMessage: 'Frestun', description: 'Notaður sem texti fyrir frestun valkost á Niðurstaða skrefi.', }, + postponedUntilVerdict: { + id: 'judicial.system.core:court.indictments.conclusion.postponed_until_verdict', + defaultMessage: 'Dómtekið', + description: + 'Notaður sem texti fyrir dómtekið valkost á Niðurstaða skrefi.', + }, complete: { id: 'judicial.system.core:court.indictments.conclusion.complete', defaultMessage: 'Lokið', description: 'Notaður sem texti fyrir lokið valkost á Niðurstaða skrefi.', }, + arrangeVerdictTitle: { + id: 'judicial.system.core:court.indictments.conclusion.arrange_verdict', + defaultMessage: 'Dómsuppkvaðning', + description: + 'Notaður sem texti fyrir Dómsuppkvaðning valkost á Niðurstaða skrefi.', + }, arrangeAnotherHearing: { id: 'judicial.system.core:court.indictments.conclusion.arrange_another_hearing', defaultMessage: 'Bóka annað þinghald', description: 'Notaður sem texti fyrir bóka annað þinghald valkost á Niðurstaða skrefi.', }, + arrangeVerdict: { + id: 'judicial.system.core:court.indictments.conclusion.postpone_until_verdict', + defaultMessage: 'Boða til dómsuppkvaðningar', + description: + 'Notaður sem texti fyrir boða til dómsuppkvaðningar valkost á Niðurstaða skrefi.', + }, postponedIndefinitely: { id: 'judicial.system.core:court.indictments.conclusion.postponed_indefinitely', defaultMessage: 'Frestað um ótilgreindan tíma', diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 02feab53e48a..89081c42a499 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -16,6 +16,7 @@ import { BlueBox, CourtArrangements, CourtCaseInfo, + DateTime, FormContentContainer, FormContext, FormFooter, @@ -39,13 +40,18 @@ import { import { strings } from './Conclusion.strings' -type Actions = 'POSTPONE' | 'REDISTRIBUTE' | 'COMPLETE' +type Actions = + | 'POSTPONE' + | 'REDISTRIBUTE' + | 'COMPLETE' + | 'POSTPONE_UNTIL_VERDICT' type Decision = | CaseIndictmentRulingDecision.FINE | CaseIndictmentRulingDecision.RULING interface Postponement { postponedIndefinitely?: boolean + postonedUntilVerdict?: boolean reason?: string } @@ -57,6 +63,7 @@ const Conclusion: React.FC = () => { const [selectedAction, setSelectedAction] = useState() const [selectedDecision, setSelectedDecision] = useState() const [postponement, setPostponement] = useState() + const { courtDate, handleCourtDateChange, @@ -217,6 +224,19 @@ const Conclusion: React.FC = () => { label={formatMessage(strings.postponed)} /> + + { + setSelectedAction('POSTPONE_UNTIL_VERDICT') + }} + large + backgroundColor="white" + label={formatMessage(strings.postponedUntilVerdict)} + /> + { )} + {selectedAction === 'POSTPONE_UNTIL_VERDICT' && ( + + + + + { + setPostponement((prev) => ({ + ...prev, + postonedUntilVerdict: !prev?.postonedUntilVerdict, + })) + }} + large + backgroundColor="white" + label={formatMessage(strings.arrangeVerdict)} + /> + + { + console.log('asd') + }} + blueBox={false} + disabled={!postponement?.postonedUntilVerdict} + /> + + + )} {selectedAction === 'COMPLETE' && ( @@ -332,7 +385,10 @@ const Conclusion: React.FC = () => { )} {selectedAction && ( - + Date: Tue, 28 May 2024 16:13:46 +0000 Subject: [PATCH 02/42] Change Radio to Checkbox --- .../src/routes/Court/Indictments/Conclusion/Conclusion.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 89081c42a499..d37a12517d03 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -325,7 +325,7 @@ const Conclusion: React.FC = () => { /> - { postonedUntilVerdict: !prev?.postonedUntilVerdict, })) }} - large backgroundColor="white" label={formatMessage(strings.arrangeVerdict)} + large + filled /> Date: Wed, 29 May 2024 10:21:02 +0000 Subject: [PATCH 03/42] Add column to Case table -- IndictmentDecision --- .../app/modules/case/dto/updateCase.input.ts | 7 ++++ .../src/app/modules/case/models/case.model.ts | 5 +++ .../migrations/20240528163733-update-case.js | 32 +++++++++++++++++++ .../app/modules/case/dto/updateCase.dto.ts | 6 ++++ .../src/app/modules/case/models/case.model.ts | 12 +++++++ .../src/components/FormProvider/case.graphql | 1 + .../utils/hooks/useCase/updateCase.graphql | 1 + libs/judicial-system/types/src/index.ts | 1 + libs/judicial-system/types/src/lib/case.ts | 7 ++++ 9 files changed, 72 insertions(+) create mode 100644 apps/judicial-system/backend/migrations/20240528163733-update-case.js diff --git a/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts b/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts index 0f1a1017aa6d..95e833d4f400 100644 --- a/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts +++ b/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts @@ -25,6 +25,7 @@ import { CaseLegalProvisions, CaseType, IndictmentCaseReviewDecision, + IndictmentDecision, RequestSharedWithDefender, SessionArrangements, UserRole, @@ -486,6 +487,12 @@ export class UpdateCaseInput { readonly indictmentReviewerId?: string @Allow() + @IsOptional() @Field(() => IndictmentCaseReviewDecision, { nullable: true }) readonly indictmentReviewDecision?: IndictmentCaseReviewDecision + + @Allow() + @IsOptional() + @Field(() => IndictmentDecision, { nullable: true }) + readonly indictmentDecision?: IndictmentDecision } diff --git a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts index 9106303bbec5..9973ee3a7191 100644 --- a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts @@ -19,6 +19,7 @@ import { CaseType, CourtDocument, IndictmentCaseReviewDecision, + IndictmentDecision, RequestSharedWithDefender, SessionArrangements, UserRole, @@ -51,6 +52,7 @@ registerEnumType(CaseIndictmentRulingDecision, { registerEnumType(IndictmentCaseReviewDecision, { name: 'IndictmentCaseReviewDecision', }) +registerEnumType(IndictmentDecision, { name: 'IndictmentDecision' }) @ObjectType() class DateLog { @@ -434,4 +436,7 @@ export class Case { @Field(() => String, { nullable: true }) readonly indictmentVerdictAppealDeadline?: string + + @Field(() => IndictmentDecision, { nullable: true }) + readonly indictmentDecision?: IndictmentDecision } diff --git a/apps/judicial-system/backend/migrations/20240528163733-update-case.js b/apps/judicial-system/backend/migrations/20240528163733-update-case.js new file mode 100644 index 000000000000..9e2e17c3de56 --- /dev/null +++ b/apps/judicial-system/backend/migrations/20240528163733-update-case.js @@ -0,0 +1,32 @@ +'use strict' + +module.exports = { + async up(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction((t) => + queryInterface.addColumn( + 'case', + 'indictment_decision', + { + type: Sequelize.STRING, + allowNull: true, + }, + { transaction: t }, + ), + ) + }, + + async down(queryInterface) { + return queryInterface.sequelize.transaction((t) => + queryInterface + .removeColumn('case', 'indictment_decision', { + transaction: t, + }) + .then(() => { + queryInterface.sequelize.query( + 'DROP TYPE "enum_case_indictment_decision";', + { transaction: t }, + ) + }), + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts b/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts index 22375301121d..f5cd707ef59d 100644 --- a/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts @@ -28,6 +28,7 @@ import { CaseType, CourtDocument, IndictmentCaseReviewDecision, + IndictmentDecision, RequestSharedWithDefender, SessionArrangements, UserRole, @@ -496,4 +497,9 @@ export class UpdateCaseDto { @IsEnum(IndictmentCaseReviewDecision) @ApiPropertyOptional({ enum: IndictmentCaseReviewDecision }) readonly indictmentReviewDecision?: IndictmentCaseReviewDecision + + @IsOptional() + @IsEnum(IndictmentDecision) + @ApiPropertyOptional({ enum: IndictmentDecision }) + readonly indictmentDecision?: IndictmentDecision } diff --git a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts index 81117d9db4cf..fbac4b0d76e2 100644 --- a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts @@ -30,6 +30,7 @@ import { CaseType, CourtDocument, IndictmentCaseReviewDecision, + IndictmentDecision, RequestSharedWithDefender, SessionArrangements, UserRole, @@ -1013,4 +1014,15 @@ export class Case extends Model { }) @ApiPropertyOptional({ enum: IndictmentCaseReviewDecision }) indictmentReviewDecision?: IndictmentCaseReviewDecision + + /********** + * The judge's pending decision in indictment cases - example: POSTPONING + **********/ + @Column({ + type: DataType.ENUM, + allowNull: true, + values: Object.values(IndictmentDecision), + }) + @ApiPropertyOptional({ enum: IndictmentDecision }) + indictmentDecision?: IndictmentDecision } diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index a38e90c4fc42..773d00f7c1be 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -263,5 +263,6 @@ query Case($input: CaseQueryInput!) { indictmentAppealDeadline indictmentVerdictViewedByAll indictmentVerdictAppealDeadline + indictmentDecision } } diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql b/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql index f9f3a850d7d6..2698876687b6 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql +++ b/apps/judicial-system/web/src/utils/hooks/useCase/updateCase.graphql @@ -219,5 +219,6 @@ mutation UpdateCase($input: UpdateCaseInput!) { } indictmentDeniedExplanation indictmentReturnedExplanation + indictmentDecision } } diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts index 2e9249225945..b66524a36e1a 100644 --- a/libs/judicial-system/types/src/index.ts +++ b/libs/judicial-system/types/src/index.ts @@ -55,6 +55,7 @@ export { restrictionCases, investigationCases, IndictmentCaseReviewDecision, + IndictmentDecision, isIndictmentCase, isRestrictionCase, isInvestigationCase, diff --git a/libs/judicial-system/types/src/lib/case.ts b/libs/judicial-system/types/src/lib/case.ts index 64aaaf215897..9aca6a3d7c5e 100644 --- a/libs/judicial-system/types/src/lib/case.ts +++ b/libs/judicial-system/types/src/lib/case.ts @@ -215,6 +215,13 @@ export enum CaseDecision { DISMISSING = 'DISMISSING', } +export enum IndictmentDecision { + POSTPONING = 'POSTPONING', + REDISTRIBUTING = 'REDISTRIBUTING', + COMPLETING = 'COMPLETING', + POSTPONING_UNTIL_VERDICT = 'POSTPONING_UNTIL_VERDICT', +} + export enum CaseAppealRulingDecision { ACCEPTING = 'ACCEPTING', REPEAL = 'REPEAL', From 4a3ec3fe7dd9498a982cda0365fde2b37beea6a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Wed, 29 May 2024 14:08:00 +0000 Subject: [PATCH 04/42] Update IndictmentDecision --- .../src/app/modules/case/guards/rolesRules.ts | 1 + .../Conclusion/Conclusion.strings.ts | 6 +- .../Indictments/Conclusion/Conclusion.tsx | 238 +++++++++++++----- .../web/src/utils/formHelper.ts | 2 + .../web/src/utils/hooks/useCase/index.ts | 9 +- libs/judicial-system/types/src/lib/case.ts | 4 +- 6 files changed, 183 insertions(+), 77 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts index c36d5d7d0266..caf236254b97 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts @@ -91,6 +91,7 @@ const districtCourtFields: (keyof UpdateCaseDto)[] = [ 'indictmentReturnedExplanation', 'postponedIndefinitelyExplanation', 'indictmentRulingDecision', + 'indictmentDecision', ] const courtOfAppealsFields: (keyof UpdateCaseDto)[] = [ diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts index 6a22a314e854..31de51f7f5dc 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts @@ -82,10 +82,10 @@ export const strings = defineMessages({ description: 'Notaður sem titill á hlaða upp takka á Niðurstaða skrefi.', }, redistribute: { - id: 'judicial.system.core:court.indictments.conclusion.redistribute', - defaultMessage: 'Fer í útlutun til dómara', + id: 'judicial.system.core:court.indictments.conclusion.redistribute_v1', + defaultMessage: 'Fer í úthlutun til dómara', description: - 'Notaður sem texti fyrir Fer í útlutun til dómara á Niðurstaða skrefi.', + 'Notaður sem texti fyrir Fer í úthlutun til dómara á Niðurstaða skrefi.', }, decision: { id: 'judicial.system.core:court.indictments.conclusion.decision', diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index d37a12517d03..521949a778db 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -30,6 +30,7 @@ import { CaseFileCategory, CaseIndictmentRulingDecision, CaseTransition, + IndictmentDecision, } from '@island.is/judicial-system-web/src/graphql/schema' import { stepValidationsType } from '@island.is/judicial-system-web/src/utils/formHelper' import { @@ -40,11 +41,6 @@ import { import { strings } from './Conclusion.strings' -type Actions = - | 'POSTPONE' - | 'REDISTRIBUTE' - | 'COMPLETE' - | 'POSTPONE_UNTIL_VERDICT' type Decision = | CaseIndictmentRulingDecision.FINE | CaseIndictmentRulingDecision.RULING @@ -60,7 +56,7 @@ const Conclusion: React.FC = () => { const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) - const [selectedAction, setSelectedAction] = useState() + const [selectedAction, setSelectedAction] = useState() const [selectedDecision, setSelectedDecision] = useState() const [postponement, setPostponement] = useState() @@ -85,76 +81,173 @@ const Conclusion: React.FC = () => { workingCase.id, ) - const handleRedistribution = useCallback(async () => { - const transitionSuccessful = await transitionCase( - workingCase.id, - CaseTransition.REDISTRIBUTE, - ) + const handleRedistribution = useCallback( + async (destination: string) => { + const transitionSuccessful = await transitionCase( + workingCase.id, + CaseTransition.REDISTRIBUTE, + ) - if (transitionSuccessful) { - router.push(constants.CASES_ROUTE) - } else { - toast.error(formatMessage(errors.transitionCase)) - } - }, [transitionCase, workingCase.id, formatMessage]) - - const handleCompletion = useCallback(async () => { - await setAndSendCaseToServer( - [ - { - indictmentRulingDecision: selectedDecision, - force: true, - }, - ], - workingCase, + const success = await setAndSendCaseToServer( + [ + { + indictmentDecision: IndictmentDecision.REDISTRIBUTING, + force: true, + }, + ], + workingCase, + setWorkingCase, + ) + + if (!transitionSuccessful || !success) { + return + } + + router.push(destination) + }, + [setAndSendCaseToServer, setWorkingCase, transitionCase, workingCase], + ) + + const handleCompletion = useCallback( + async (destination: string) => { + const success = await setAndSendCaseToServer( + [ + { + indictmentRulingDecision: selectedDecision, + indictmentDecision: IndictmentDecision.COMPLETING, + force: true, + }, + ], + workingCase, + setWorkingCase, + ) + + if (!success) { + return + } + + router.push(`${destination}/${workingCase.id}`) + }, + [selectedDecision, setAndSendCaseToServer, setWorkingCase, workingCase], + ) + + const handlePostponementUntilVerdict = useCallback( + async (destination: string) => { + const success = await setAndSendCaseToServer( + [ + { + indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, + force: true, + }, + ], + workingCase, + setWorkingCase, + ) + + if (!success) { + return + } + + router.push(`${destination}/${workingCase.id}`) + }, + [setAndSendCaseToServer, setWorkingCase, workingCase], + ) + + const handlePostponementIndefinitely = useCallback( + async (destination: string) => { + const updateSuccess = await setAndSendCaseToServer( + [ + { + courtDate: null, + postponedIndefinitelyExplanation: postponement?.reason, + }, + ], + workingCase, + setWorkingCase, + ) + + if (!updateSuccess) { + return + } + + router.push(`${destination}/${workingCase.id}`) + }, + [postponement?.reason, setAndSendCaseToServer, setWorkingCase, workingCase], + ) + + const handlePostponement = useCallback( + async (destination: string) => { + const updateCourtDateSuccess = await sendCourtDateToServer([ + { postponedIndefinitelyExplanation: null, force: true }, + ]) + + const updateSuccess = await setAndSendCaseToServer( + [ + { + indictmentDecision: IndictmentDecision.POSTPONING, + }, + ], + workingCase, + setWorkingCase, + ) + + if (!updateCourtDateSuccess || !updateSuccess) { + return + } + + router.push(`${destination}/${workingCase.id}`) + }, + [ + sendCourtDateToServer, + setAndSendCaseToServer, setWorkingCase, - ) - }, [selectedDecision, setAndSendCaseToServer, setWorkingCase, workingCase]) + workingCase, + ], + ) const handleNavigationTo = useCallback( async (destination: keyof stepValidationsType) => { - if (selectedAction === 'REDISTRIBUTE') { - handleRedistribution() - } else if (selectedAction === 'COMPLETE') { - handleCompletion() + if (selectedAction === IndictmentDecision.REDISTRIBUTING) { + handleRedistribution(destination) + } else if (selectedAction === IndictmentDecision.COMPLETING) { + handleCompletion(destination) + } else if ( + selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT + ) { + await handlePostponementUntilVerdict(destination) } else if (postponement?.postponedIndefinitely) { - const updateSuccess = await updateCase(workingCase.id, { - courtDate: null, - postponedIndefinitelyExplanation: postponement.reason, - }) - - if (!updateSuccess) { - return - } + handlePostponementIndefinitely(destination) } else { - await sendCourtDateToServer([ - { postponedIndefinitelyExplanation: null, force: true }, - ]) + handlePostponement(destination) } - - router.push(`${destination}/${workingCase.id}`) }, [ selectedAction, postponement?.postponedIndefinitely, - postponement?.reason, - workingCase.id, handleRedistribution, handleCompletion, - updateCase, - sendCourtDateToServer, + handlePostponementUntilVerdict, + handlePostponementIndefinitely, + handlePostponement, ], ) + const isDecisionChecked = (decision: IndictmentDecision) => { + return ( + selectedAction === decision || + (!selectedAction && workingCase.indictmentDecision === decision) + ) + } + useEffect(() => { if (workingCase.indictmentRulingDecision) { setSelectedDecision(workingCase.indictmentRulingDecision) - setSelectedAction('COMPLETE') + setSelectedAction(IndictmentDecision.COMPLETING) } else if ( workingCase.courtDate?.date || workingCase.postponedIndefinitelyExplanation ) { - setSelectedAction('POSTPONE') + setSelectedAction(IndictmentDecision.POSTPONING) if (workingCase.postponedIndefinitelyExplanation) { setPostponement({ @@ -176,11 +269,11 @@ const Conclusion: React.FC = () => { return false } - if (selectedAction === 'REDISTRIBUTE') { + if (selectedAction === IndictmentDecision.REDISTRIBUTING) { return uploadFiles.find( (file) => file.category === CaseFileCategory.COURT_RECORD, ) - } else if (selectedAction === 'POSTPONE') { + } else if (selectedAction === IndictmentDecision.POSTPONING) { return ( Boolean( postponement?.postponedIndefinitely @@ -188,8 +281,11 @@ const Conclusion: React.FC = () => { : courtDate?.date, ) && allFilesDoneOrError ) - } else if (selectedAction === 'COMPLETE') { - return selectedDecision !== undefined && allFilesDoneOrError + } else if ( + selectedAction === IndictmentDecision.COMPLETING || + selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT + ) { + return allFilesDoneOrError } } @@ -215,9 +311,9 @@ const Conclusion: React.FC = () => { { - setSelectedAction('POSTPONE') + setSelectedAction(IndictmentDecision.POSTPONING) }} large backgroundColor="white" @@ -228,9 +324,11 @@ const Conclusion: React.FC = () => { { - setSelectedAction('POSTPONE_UNTIL_VERDICT') + setSelectedAction(IndictmentDecision.POSTPONING_UNTIL_VERDICT) }} large backgroundColor="white" @@ -241,9 +339,9 @@ const Conclusion: React.FC = () => { { - setSelectedAction('COMPLETE') + setSelectedAction(IndictmentDecision.COMPLETING) }} large backgroundColor="white" @@ -253,9 +351,9 @@ const Conclusion: React.FC = () => { { - setSelectedAction('REDISTRIBUTE') + setSelectedAction(IndictmentDecision.REDISTRIBUTING) }} large backgroundColor="white" @@ -263,7 +361,7 @@ const Conclusion: React.FC = () => { /> - {selectedAction === 'POSTPONE' && ( + {selectedAction === IndictmentDecision.POSTPONING && ( <> { )} - {selectedAction === 'POSTPONE_UNTIL_VERDICT' && ( + {selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT && ( { )} - {selectedAction === 'COMPLETE' && ( + {selectedAction === IndictmentDecision.COMPLETING && ( @@ -392,7 +490,7 @@ const Conclusion: React.FC = () => { > { previousUrl={`${constants.INDICTMENTS_DEFENDER_ROUTE}/${workingCase.id}`} onNextButtonClick={() => handleNavigationTo( - selectedAction === 'COMPLETE' + selectedAction === IndictmentDecision.COMPLETING ? constants.INDICTMENTS_SUMMARY_ROUTE + : selectedAction === IndictmentDecision.REDISTRIBUTING + ? constants.CASES_ROUTE : constants.INDICTMENTS_COURT_OVERVIEW_ROUTE, ) } diff --git a/apps/judicial-system/web/src/utils/formHelper.ts b/apps/judicial-system/web/src/utils/formHelper.ts index bc47dc522288..0520df1ae177 100644 --- a/apps/judicial-system/web/src/utils/formHelper.ts +++ b/apps/judicial-system/web/src/utils/formHelper.ts @@ -137,6 +137,7 @@ export const hasDateChanged = ( } export type stepValidationsType = { + [constants.CASES_ROUTE]: () => boolean [constants.CREATE_RESTRICTION_CASE_ROUTE]: (theCase: Case) => boolean [constants.CREATE_TRAVEL_BAN_ROUTE]: (theCase: Case) => boolean [constants.RESTRICTION_CASE_DEFENDANT_ROUTE]: (theCase: Case) => boolean @@ -202,6 +203,7 @@ export type stepValidationsType = { export const stepValidations = (): stepValidationsType => { return { + [constants.CASES_ROUTE]: () => true, [constants.CREATE_RESTRICTION_CASE_ROUTE]: (theCase: Case) => validations.isDefendantStepValidRC(theCase, theCase.policeCaseNumbers), [constants.CREATE_TRAVEL_BAN_ROUTE]: (theCase: Case) => diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts index 8a2d6e6e4ab0..9f0e235aa7e8 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts @@ -385,7 +385,7 @@ const useCase = () => { delete updatesToCase.force if (Object.keys(updatesToCase).length === 0) { - return + return false } setWorkingCase((prevWorkingCase) => ({ @@ -394,16 +394,19 @@ const useCase = () => { })) if (!workingCase.id) { - return + return false } const newWorkingCase = await updateCase(workingCase.id, updatesToCase) if (!newWorkingCase) { - throw new Error() + return false } + + return true } catch (error) { toast.error(formatMessage(errors.updateCase)) + return false } } diff --git a/libs/judicial-system/types/src/lib/case.ts b/libs/judicial-system/types/src/lib/case.ts index 9aca6a3d7c5e..618377f942b1 100644 --- a/libs/judicial-system/types/src/lib/case.ts +++ b/libs/judicial-system/types/src/lib/case.ts @@ -217,9 +217,9 @@ export enum CaseDecision { export enum IndictmentDecision { POSTPONING = 'POSTPONING', - REDISTRIBUTING = 'REDISTRIBUTING', - COMPLETING = 'COMPLETING', POSTPONING_UNTIL_VERDICT = 'POSTPONING_UNTIL_VERDICT', + COMPLETING = 'COMPLETING', + REDISTRIBUTING = 'REDISTRIBUTING', } export enum CaseAppealRulingDecision { From 9061138baf01eb3cce432ac95c0e5c203c253d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 30 May 2024 13:48:41 +0000 Subject: [PATCH 05/42] Postponed until verdict tag in cases --- .../case-list/models/caseList.model.ts | 4 + .../case/interceptors/caseList.interceptor.ts | 1 + .../TagCaseState/TagCaseState.strings.ts | 5 ++ .../components/TagCaseState/TagCaseState.tsx | 81 +++++++++++-------- .../src/routes/Shared/Cases/ActiveCases.tsx | 1 + .../web/src/routes/Shared/Cases/cases.graphql | 1 + 6 files changed, 58 insertions(+), 35 deletions(-) diff --git a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts index fda810ed9962..c5f5e7aa020a 100644 --- a/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts +++ b/apps/judicial-system/api/src/app/modules/case-list/models/caseList.model.ts @@ -8,6 +8,7 @@ import { CaseState, CaseType, IndictmentCaseReviewDecision, + IndictmentDecision, } from '@island.is/judicial-system/types' import { Defendant } from '../../defendant' @@ -120,4 +121,7 @@ export class CaseListEntry { @Field(() => String, { nullable: true }) readonly indictmentVerdictAppealDeadline?: string + + @Field(() => IndictmentDecision, { nullable: true }) + readonly indictmentDecision?: IndictmentDecision } diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts index af0efc03b223..0ca1727294c4 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts @@ -58,6 +58,7 @@ export class CaseListInterceptor implements NestInterceptor { )?.comment, indictmentReviewer: theCase.indictmentReviewer, indictmentReviewDecision: theCase.indictmentReviewDecision, + indictmentDecision: theCase.indictmentDecision, } }), ), diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts index 2bcb77190931..d6d841b7e06b 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts @@ -67,4 +67,9 @@ export const strings = defineMessages({ defaultMessage: 'Móttekið', description: 'Notað sem merki þegar mál í stöðu "Móttekið" í málalista', }, + postponedUntilVerdict: { + id: 'judicial.system.core:tag_case_state.postponed_until_verdict', + defaultMessage: 'Dómtekið', + description: 'Notað sem merki þegar mál í stöðu "Dómtekið" í málalista', + }, }) diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx index e920234c4642..568fe454d70e 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { FC } from 'react' import { IntlShape, useIntl } from 'react-intl' import { Tag, TagVariant } from '@island.is/island-ui/core' @@ -9,6 +9,7 @@ import { import { CaseState, CaseType, + IndictmentDecision, User, } from '@island.is/judicial-system-web/src/graphql/schema' @@ -26,6 +27,7 @@ interface Props { state?: CaseState | null, indictmentReviewer?: User | null, // TODO: Refactor so we have a more generalized interface for the info passed in to the component ) => { color: TagVariant; text: string } + indictmentDecision?: IndictmentDecision | null } export const mapIndictmentCaseStateToTagVariant = ( @@ -53,43 +55,50 @@ export const mapCaseStateToTagVariant = ( isValidToDateInThePast?: boolean | null, scheduledDate?: string | null, isCourtRole?: boolean, + indictmentDecision?: IndictmentDecision | null, ): { color: TagVariant; text: string } => { - switch (state) { - case CaseState.NEW: - case CaseState.DRAFT: - case CaseState.WAITING_FOR_CONFIRMATION: - return { color: 'red', text: formatMessage(strings.draft) } - case CaseState.SUBMITTED: - return { - color: 'purple', - text: formatMessage(isCourtRole ? strings.new : strings.sent), - } - case CaseState.RECEIVED: - return scheduledDate - ? { color: 'mint', text: formatMessage(strings.scheduled) } - : { color: 'blueberry', text: formatMessage(strings.received) } - case CaseState.MAIN_HEARING: - return { color: 'blue', text: formatMessage(strings.reassignment) } - case CaseState.ACCEPTED: - case CaseState.COMPLETED: - return isIndictmentCase(caseType) || isValidToDateInThePast - ? { color: 'darkerBlue', text: formatMessage(strings.inactive) } - : { - color: 'blue', - text: formatMessage( - isInvestigationCase(caseType) ? strings.accepted : strings.active, - ), - } - case CaseState.REJECTED: - return { color: 'rose', text: formatMessage(strings.rejected) } - case CaseState.DISMISSED: - return { color: 'dark', text: formatMessage(strings.dismissed) } - default: - return { color: 'white', text: formatMessage(strings.unknown) } + if (indictmentDecision === IndictmentDecision.POSTPONING_UNTIL_VERDICT) { + return { color: 'mint', text: formatMessage(strings.postponedUntilVerdict) } + } else { + switch (state) { + case CaseState.NEW: + case CaseState.DRAFT: + case CaseState.WAITING_FOR_CONFIRMATION: + return { color: 'red', text: formatMessage(strings.draft) } + case CaseState.SUBMITTED: + return { + color: 'purple', + text: formatMessage(isCourtRole ? strings.new : strings.sent), + } + case CaseState.RECEIVED: + return scheduledDate + ? { color: 'mint', text: formatMessage(strings.scheduled) } + : { color: 'blueberry', text: formatMessage(strings.received) } + case CaseState.MAIN_HEARING: + return { color: 'blue', text: formatMessage(strings.reassignment) } + case CaseState.ACCEPTED: + case CaseState.COMPLETED: + return isIndictmentCase(caseType) || isValidToDateInThePast + ? { color: 'darkerBlue', text: formatMessage(strings.inactive) } + : { + color: 'blue', + text: formatMessage( + isInvestigationCase(caseType) + ? strings.accepted + : strings.active, + ), + } + case CaseState.REJECTED: + return { color: 'rose', text: formatMessage(strings.rejected) } + case CaseState.DISMISSED: + return { color: 'dark', text: formatMessage(strings.dismissed) } + default: + return { color: 'white', text: formatMessage(strings.unknown) } + } } } -const TagCaseState: React.FC> = (Props) => { +const TagCaseState: FC = (props) => { const { formatMessage } = useIntl() const { caseState, @@ -99,7 +108,8 @@ const TagCaseState: React.FC> = (Props) => { courtDate, indictmentReviewer, customMapCaseStateToTag, - } = Props + indictmentDecision, + } = props const tagVariant = customMapCaseStateToTag ? customMapCaseStateToTag(formatMessage, caseState, indictmentReviewer) @@ -110,6 +120,7 @@ const TagCaseState: React.FC> = (Props) => { isValidToDateInThePast, courtDate, isCourtRole, + indictmentDecision, ) if (!tagVariant) return null diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx index 1c52098aa36d..b7dd231cba17 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx @@ -326,6 +326,7 @@ const ActiveCases: React.FC> = (props) => { isCourtRole={isDistrictCourtUser(user)} isValidToDateInThePast={c.isValidToDateInThePast} courtDate={c.courtDate} + indictmentDecision={c.indictmentDecision} /> {c.appealState && ( diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql index 2fd62e99ff51..81ec77b8733a 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql @@ -92,5 +92,6 @@ query Cases { indictmentAppealDeadline indictmentVerdictViewedByAll indictmentVerdictAppealDeadline + indictmentDecision } } From d0ff81b4df2d139d7ec375ec8e2560a7043b98b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 30 May 2024 14:11:10 +0000 Subject: [PATCH 06/42] Disable subpoena screen if a case is postponed until verdict --- .../Court/Indictments/Subpoena/Subpoena.tsx | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx index 54a951301427..084b3c9078b8 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx @@ -18,7 +18,10 @@ import { SectionHeading, useCourtArrangements, } from '@island.is/judicial-system-web/src/components' -import { NotificationType } from '@island.is/judicial-system-web/src/graphql/schema' +import { + IndictmentDecision, + NotificationType, +} from '@island.is/judicial-system-web/src/graphql/schema' import type { stepValidationsType } from '@island.is/judicial-system-web/src/utils/formHelper' import { useCase } from '@island.is/judicial-system-web/src/utils/hooks' import { hasSentNotification } from '@island.is/judicial-system-web/src/utils/stepHelper' @@ -40,8 +43,20 @@ const Subpoena: React.FC> = () => { } = useCourtArrangements(workingCase, setWorkingCase, 'arraignmentDate') const { sendNotification } = useCase() + const isPostponed = Boolean( + workingCase.indictmentDecision === + IndictmentDecision.POSTPONING_UNTIL_VERDICT || + workingCase.courtDate?.date || + workingCase.postponedIndefinitelyExplanation, + ) + const handleNavigationTo = useCallback( async (destination: keyof stepValidationsType) => { + if (isPostponed) { + router.push(`${destination}/${workingCase.id}`) + return + } + await sendCourtDateToServer() if ( @@ -57,6 +72,7 @@ const Subpoena: React.FC> = () => { } }, [ + isPostponed, sendCourtDateToServer, workingCase.notifications, workingCase.id, @@ -65,9 +81,6 @@ const Subpoena: React.FC> = () => { ) const stepIsValid = isSubpoenaStepValid(workingCase, courtDate?.date) - const isPostponed = Boolean( - workingCase.courtDate?.date || workingCase.postponedIndefinitelyExplanation, - ) return ( > = () => { previousUrl={`${constants.INDICTMENTS_RECEPTION_AND_ASSIGNMENT_ROUTE}/${workingCase.id}`} nextIsLoading={isLoadingWorkingCase} onNextButtonClick={() => { - if (isPostponed) { - router.push( - `${constants.INDICTMENTS_DEFENDER_ROUTE}/${workingCase.id}`, - ) - } else { - handleNavigationTo(constants.INDICTMENTS_DEFENDER_ROUTE) - } + handleNavigationTo(constants.INDICTMENTS_DEFENDER_ROUTE) }} nextButtonText={ isPostponed ? undefined : formatMessage(strings.nextButtonText) From f9119f8920693da9397ea0647fa505c92b8f00d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 30 May 2024 14:36:26 +0000 Subject: [PATCH 07/42] Add a postpone until verdict tag for defenders --- apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx | 4 ++-- .../routes/Defender/Cases/components/DefenderCasesTable.tsx | 1 + .../web/src/routes/Defender/Cases/defenderCases.graphql | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx index 6db5fdd17694..3fde1fce30e1 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { FC, useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import partition from 'lodash/partition' @@ -17,7 +17,7 @@ import { useDefenderCasesQuery } from './defenderCases.generated' import { defenderCases as m } from './Cases.strings' import * as styles from './Cases.css' -export const Cases: React.FC> = () => { +export const Cases: FC = () => { const { formatMessage } = useIntl() const availableTabs = ['active', 'completed'] diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx index 0e6e2b411cf6..3732026e2fe7 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx @@ -158,6 +158,7 @@ export const DefenderCasesTable: React.FC> = ( caseType={column.type} isValidToDateInThePast={column.isValidToDateInThePast} courtDate={column.courtDate} + indictmentDecision={column.indictmentDecision} /> {column.appealState && ( diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql b/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql index 230b576c0c32..04ccc2539fe9 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql +++ b/apps/judicial-system/web/src/routes/Defender/Cases/defenderCases.graphql @@ -27,5 +27,6 @@ query DefenderCases($input: CaseListQueryInput) { initialRulingDate rulingDate postponedIndefinitelyExplanation + indictmentDecision } } From c5e92f5b11a0d57dc6a8654e109b6d470283da09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 30 May 2024 15:14:53 +0000 Subject: [PATCH 08/42] Checkpoint --- .../Conclusion/Conclusion.strings.ts | 5 ++ .../Indictments/Conclusion/Conclusion.tsx | 54 ++++++++++++++----- 2 files changed, 46 insertions(+), 13 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts index 31de51f7f5dc..9cd43f90e31b 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.strings.ts @@ -109,4 +109,9 @@ export const strings = defineMessages({ description: 'Notaður sem texti í viðurlagaákvörðun valmöguleika á Niðurstaða skrefi.', }, + nextButtonTextConfirm: { + id: 'judicial.system.core:court.indictments.conclusion.next_button_text_confirm', + defaultMessage: 'Staðfesta', + description: 'Notaður sem texti á halda áfram takka.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 521949a778db..c07a2bad14a3 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -232,17 +232,20 @@ const Conclusion: React.FC = () => { ], ) - const isDecisionChecked = (decision: IndictmentDecision) => { - return ( - selectedAction === decision || - (!selectedAction && workingCase.indictmentDecision === decision) - ) - } - useEffect(() => { + if ( + workingCase.indictmentDecision === + IndictmentDecision.POSTPONING_UNTIL_VERDICT + ) { + setSelectedAction(IndictmentDecision.POSTPONING_UNTIL_VERDICT) + + return + } if (workingCase.indictmentRulingDecision) { setSelectedDecision(workingCase.indictmentRulingDecision) setSelectedAction(IndictmentDecision.COMPLETING) + + return } else if ( workingCase.courtDate?.date || workingCase.postponedIndefinitelyExplanation @@ -262,6 +265,7 @@ const Conclusion: React.FC = () => { workingCase.courtDate?.date, workingCase.postponedIndefinitelyExplanation, workingCase.indictmentRulingDecision, + workingCase.indictmentDecision, ]) const stepIsValid = () => { @@ -311,7 +315,12 @@ const Conclusion: React.FC = () => { { setSelectedAction(IndictmentDecision.POSTPONING) }} @@ -324,9 +333,13 @@ const Conclusion: React.FC = () => { { setSelectedAction(IndictmentDecision.POSTPONING_UNTIL_VERDICT) }} @@ -339,7 +352,12 @@ const Conclusion: React.FC = () => { { setSelectedAction(IndictmentDecision.COMPLETING) }} @@ -351,7 +369,12 @@ const Conclusion: React.FC = () => { { setSelectedAction(IndictmentDecision.REDISTRIBUTING) }} @@ -551,6 +574,11 @@ const Conclusion: React.FC = () => { : constants.INDICTMENTS_COURT_OVERVIEW_ROUTE, ) } + nextButtonText={ + selectedAction === IndictmentDecision.COMPLETING + ? undefined + : formatMessage(strings.nextButtonTextConfirm) + } nextIsDisabled={!stepIsValid()} nextIsLoading={isUpdatingCase} /> From d75eeba26375e146485ba63d2ace587134ac42a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Thu, 30 May 2024 15:33:08 +0000 Subject: [PATCH 09/42] Checkpoint --- .../Court/Indictments/Conclusion/Conclusion.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index c07a2bad14a3..8e58ddf4c7cf 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -67,8 +67,7 @@ const Conclusion: React.FC = () => { sendCourtDateToServer, } = useCourtArrangements(workingCase, setWorkingCase, 'courtDate') - const { updateCase, isUpdatingCase, transitionCase, setAndSendCaseToServer } = - useCase() + const { isUpdatingCase, transitionCase, setAndSendCaseToServer } = useCase() const { uploadFiles, @@ -143,14 +142,20 @@ const Conclusion: React.FC = () => { workingCase, setWorkingCase, ) + const updateCourtDateSuccess = await sendCourtDateToServer() - if (!success) { + if (!success || !updateCourtDateSuccess) { return } router.push(`${destination}/${workingCase.id}`) }, - [setAndSendCaseToServer, setWorkingCase, workingCase], + [ + sendCourtDateToServer, + setAndSendCaseToServer, + setWorkingCase, + workingCase, + ], ) const handlePostponementIndefinitely = useCallback( From e73e0377f6f54be24e43630837b392d52138fe53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 31 May 2024 09:08:51 +0000 Subject: [PATCH 10/42] Merge --- .../Court/Indictments/Conclusion/Conclusion.tsx | 12 ++++-------- .../Defender/Cases/components/DefenderCasesTable.tsx | 3 --- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 66f883d51401..7549534b9496 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -281,19 +281,19 @@ const Conclusion: React.FC = () => { } switch (selectedAction) { - case 'POSTPONE': + case IndictmentDecision.POSTPONING: return Boolean( postponement?.postponedIndefinitely ? postponement.reason : courtDate?.date, ) - case 'REDISTRIBUTE': + case IndictmentDecision.REDISTRIBUTING: return uploadFiles.some( (file) => file.category === CaseFileCategory.COURT_RECORD && file.status === 'done', ) - case 'COMPLETE': + case IndictmentDecision.COMPLETING: switch (selectedDecision) { case CaseIndictmentRulingDecision.RULING: case CaseIndictmentRulingDecision.DISMISSAL: @@ -576,11 +576,7 @@ const Conclusion: React.FC = () => { > >>>>>> 8c99a2ba5c7c08486b09c31f3276d3487329f0f3 /> { /> )} - {selectedAction === 'COMPLETE' && + {selectedAction === IndictmentDecision.COMPLETING && (selectedDecision === CaseIndictmentRulingDecision.RULING || selectedDecision === CaseIndictmentRulingDecision.DISMISSAL) && ( diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx index 9af67c73de40..53357ebb4dc9 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/components/DefenderCasesTable.tsx @@ -158,11 +158,8 @@ export const DefenderCasesTable: React.FC> = ( caseType={column.type} isValidToDateInThePast={column.isValidToDateInThePast} courtDate={column.courtDate} -<<<<<<< HEAD indictmentDecision={column.indictmentDecision} -======= indictmentRulingDecision={column.indictmentRulingDecision} ->>>>>>> 8c99a2ba5c7c08486b09c31f3276d3487329f0f3 /> {column.appealState && ( From ed6ba894b045cba33bee62a5e0b87fa7e122ed79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 31 May 2024 11:06:20 +0000 Subject: [PATCH 11/42] Determining when step is valid when postponing until verdict --- .../Indictments/Conclusion/Conclusion.tsx | 96 ++++++++++--------- 1 file changed, 51 insertions(+), 45 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 7549534b9496..3deb874d94ec 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -49,7 +49,7 @@ type Decision = interface Postponement { postponedIndefinitely?: boolean - postonedUntilVerdict?: boolean + isSettingVerdictDate?: boolean reason?: string } @@ -61,6 +61,7 @@ const Conclusion: React.FC = () => { const [selectedAction, setSelectedAction] = useState() const [selectedDecision, setSelectedDecision] = useState() const [postponement, setPostponement] = useState() + const [selectedCourtDate, setSelectedCourtDate] = useState() const { courtDate, @@ -276,51 +277,55 @@ const Conclusion: React.FC = () => { ]) const stepIsValid = () => { - if (!allFilesDoneOrError) { + if (selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT) { + return postponement?.isSettingVerdictDate + ? Boolean(selectedCourtDate) + : false + } else if (!allFilesDoneOrError) { return false - } - - switch (selectedAction) { - case IndictmentDecision.POSTPONING: - return Boolean( - postponement?.postponedIndefinitely - ? postponement.reason - : courtDate?.date, - ) - case IndictmentDecision.REDISTRIBUTING: - return uploadFiles.some( - (file) => - file.category === CaseFileCategory.COURT_RECORD && - file.status === 'done', - ) - case IndictmentDecision.COMPLETING: - switch (selectedDecision) { - case CaseIndictmentRulingDecision.RULING: - case CaseIndictmentRulingDecision.DISMISSAL: - return ( - uploadFiles.some( + } else { + switch (selectedAction) { + case IndictmentDecision.POSTPONING: + return Boolean( + postponement?.postponedIndefinitely + ? postponement.reason + : courtDate?.date, + ) + case IndictmentDecision.REDISTRIBUTING: + return uploadFiles.some( + (file) => + file.category === CaseFileCategory.COURT_RECORD && + file.status === 'done', + ) + case IndictmentDecision.COMPLETING: + switch (selectedDecision) { + case CaseIndictmentRulingDecision.RULING: + case CaseIndictmentRulingDecision.DISMISSAL: + return ( + uploadFiles.some( + (file) => + file.category === CaseFileCategory.COURT_RECORD && + file.status === 'done', + ) && + uploadFiles.some( + (file) => + file.category === CaseFileCategory.RULING && + file.status === 'done', + ) + ) + case CaseIndictmentRulingDecision.FINE: + case CaseIndictmentRulingDecision.CANCELLATION: + return uploadFiles.some( (file) => file.category === CaseFileCategory.COURT_RECORD && file.status === 'done', - ) && - uploadFiles.some( - (file) => - file.category === CaseFileCategory.RULING && - file.status === 'done', ) - ) - case CaseIndictmentRulingDecision.FINE: - case CaseIndictmentRulingDecision.CANCELLATION: - return uploadFiles.some( - (file) => - file.category === CaseFileCategory.COURT_RECORD && - file.status === 'done', - ) - default: - return false - } - default: - return false + default: + return false + } + default: + return false + } } } @@ -480,11 +485,11 @@ const Conclusion: React.FC = () => { { setPostponement((prev) => ({ ...prev, - postonedUntilVerdict: !prev?.postonedUntilVerdict, + isSettingVerdictDate: !prev?.isSettingVerdictDate, })) }} backgroundColor="white" @@ -496,10 +501,11 @@ const Conclusion: React.FC = () => { { - console.log('asd') + setSelectedCourtDate(date) }} blueBox={false} - disabled={!postponement?.postonedUntilVerdict} + disabled={!postponement?.isSettingVerdictDate} + required={postponement?.isSettingVerdictDate} /> From 7cf3da02320ee03eec8acb53b0df097ad3ce0c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 31 May 2024 11:47:04 +0000 Subject: [PATCH 12/42] Checkpoint --- .../Indictments/Conclusion/Conclusion.tsx | 2 +- .../Indictments/Summary/Summary.strings.ts | 10 ++++++++++ .../Court/Indictments/Summary/Summary.tsx | 19 ++++++++++++++++--- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 3deb874d94ec..b27ce2b28ee3 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -280,7 +280,7 @@ const Conclusion: React.FC = () => { if (selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT) { return postponement?.isSettingVerdictDate ? Boolean(selectedCourtDate) - : false + : true } else if (!allFilesDoneOrError) { return false } else { diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts index fc389fc02139..b5677d2c62ce 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.strings.ts @@ -45,4 +45,14 @@ export const strings = defineMessages({ 'Gögn hafa verið send á ákæranda og verjanda hafi þeim verið hlaðið upp.', description: 'Notaður sem texti í staðfestingarglugga um að mál sé lokið.', }, + scheduledInfoCardTitle: { + id: 'judicial.system.core:indictments.summary.scheduled_info_card_title', + defaultMessage: 'Á dagskrá', + description: 'Notaður sem titill á Á dagskrá upplýsingakorti.', + }, + scheduledInfoCardValue: { + id: 'judicial.system.core:indictments.summary.scheduled_info_card_value', + defaultMessage: 'Málið er dómtekið', + description: 'Notaður sem gildi á Á dagskrá upplýsingakorti.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index 3e4630c1ad1d..8c96c6a23134 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -9,6 +9,8 @@ import { FormContentContainer, FormContext, FormFooter, + InfoCard, + InfoCardCaseScheduledIndictment, InfoCardClosedIndictment, Modal, PageHeader, @@ -85,18 +87,29 @@ const Summary: React.FC = () => { {formatMessage(strings.title)} - + {formatMessage(core.caseNumber, { caseNumber: workingCase.courtCaseNumber, })} - + - + + + + From 2b5c478143945d78d8f526986d7224ca941cd68c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 31 May 2024 11:57:45 +0000 Subject: [PATCH 13/42] Change all instances of Array to something[] --- .../src/app/modules/case/limitedAccessCase.service.ts | 6 ++---- .../web/src/components/InfoCard/InfoCard.tsx | 6 +++--- apps/judicial-system/web/src/utils/hooks/useCase/index.ts | 5 +---- apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx | 2 +- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts index 1be2398a862e..3cef3a2f246c 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts @@ -377,9 +377,7 @@ export class LimitedAccessCaseService { }) } - private zipFiles( - files: Array<{ data: Buffer; name: string }>, - ): Promise { + private zipFiles(files: { data: Buffer; name: string }[]): Promise { return new Promise((resolve, reject) => { const buffs: Buffer[] = [] const converter = new Writable() @@ -410,7 +408,7 @@ export class LimitedAccessCaseService { } async getAllFilesZip(theCase: Case, user: TUser): Promise { - const filesToZip: Array<{ data: Buffer; name: string }> = [] + const filesToZip: { data: Buffer; name: string }[] = [] const caseFilesByCategory = theCase.caseFiles?.filter( diff --git a/apps/judicial-system/web/src/components/InfoCard/InfoCard.tsx b/apps/judicial-system/web/src/components/InfoCard/InfoCard.tsx index 75a92e5a2259..32a9c07c9d6d 100644 --- a/apps/judicial-system/web/src/components/InfoCard/InfoCard.tsx +++ b/apps/judicial-system/web/src/components/InfoCard/InfoCard.tsx @@ -28,12 +28,12 @@ interface UniqueDefendersProps { } interface DataSection { - data: Array<{ title: string; value?: React.ReactNode }> + data: { title: string; value?: React.ReactNode }[] } interface Props { - courtOfAppealData?: Array<{ title: string; value?: React.ReactNode }> - data: Array<{ title: string; value?: React.ReactNode }> + courtOfAppealData?: { title: string; value?: React.ReactNode }[] + data: { title: string; value?: React.ReactNode }[] defendants?: { title: string items: Defendant[] diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts index 8a2d6e6e4ab0..55cea551369b 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts @@ -109,10 +109,7 @@ export const update = (update: UpdateCase, workingCase: Case): UpdateCase => { return validUpdates } -export const formatUpdates = ( - updates: Array, - workingCase: Case, -) => { +export const formatUpdates = (updates: UpdateCase[], workingCase: Case) => { const changes: UpdateCase[] = updates.map((entry) => { if (entry.force) { return overwrite(entry) diff --git a/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx b/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx index 0e926219eea2..47a2352c4878 100644 --- a/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx +++ b/apps/judicial-system/web/src/utils/hooks/useDeb/index.tsx @@ -4,7 +4,7 @@ import { TempCase as Case } from '@island.is/judicial-system-web/src/types' import useCase from '../useCase' -const useDeb = (workingCase: Case, keys: Array | keyof Case) => { +const useDeb = (workingCase: Case, keys: (keyof Case)[] | keyof Case) => { const { updateCase } = useCase() const newKeys = Array.isArray(keys) ? keys : [keys] From 164ea3363de08a948a7f33046b99a8e0080b84df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Fri, 31 May 2024 14:39:33 +0000 Subject: [PATCH 14/42] Only show InfoCard on summary page if indictment decison is POSTPONING_UNTIL_VERDICT --- .../Court/Indictments/Summary/Summary.tsx | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx index 8c96c6a23134..5ef6f1379b54 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Summary/Summary.tsx @@ -10,7 +10,6 @@ import { FormContext, FormFooter, InfoCard, - InfoCardCaseScheduledIndictment, InfoCardClosedIndictment, Modal, PageHeader, @@ -27,6 +26,7 @@ import { CaseFile, CaseFileCategory, CaseTransition, + IndictmentDecision, } from '@island.is/judicial-system-web/src/graphql/schema' import { useCase, @@ -98,17 +98,20 @@ const Summary: React.FC = () => { - - - + {workingCase.indictmentDecision === + IndictmentDecision.POSTPONING_UNTIL_VERDICT && ( + + + + )} From 8b6c049ce708eeb7332a2b15ef67c421da64693d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 11:48:07 +0000 Subject: [PATCH 15/42] Wipe courtDate out when postponing until verdict --- .../Court/Indictments/Conclusion/Conclusion.tsx | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index b27ce2b28ee3..f596efeb7180 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -139,26 +139,21 @@ const Conclusion: React.FC = () => { [ { indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, + courtDate: null, force: true, }, ], workingCase, setWorkingCase, ) - const updateCourtDateSuccess = await sendCourtDateToServer() - if (!success || !updateCourtDateSuccess) { + if (!success) { return } router.push(`${destination}/${workingCase.id}`) }, - [ - sendCourtDateToServer, - setAndSendCaseToServer, - setWorkingCase, - workingCase, - ], + [setAndSendCaseToServer, setWorkingCase, workingCase], ) const handlePostponementIndefinitely = useCallback( From a01f34c0e7092a1b3f37e6693ad564f8647ef64d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 11:56:55 +0000 Subject: [PATCH 16/42] Move scheduled info card to overview page --- .../Indictments/Overview/Overview.strings.ts | 10 ++++++++++ .../Court/Indictments/Overview/Overview.tsx | 20 ++++++++++++++++++- .../Indictments/Summary/Summary.strings.ts | 10 ---------- .../Court/Indictments/Summary/Summary.tsx | 16 --------------- 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts index 86819bf38a17..a813ba6954bf 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.strings.ts @@ -35,4 +35,14 @@ export const strings = defineMessages({ description: 'Notaður sem texti á takka í staðfestingarglugga um að mál hafi verið sent til Ríkissaksóknara.', }, + scheduledInfoCardTitle: { + id: 'judicial.system.core:indictment_overview.scheduled_info_card_title', + defaultMessage: 'Á dagskrá', + description: 'Notaður sem titill á Á dagskrá upplýsingakorti.', + }, + scheduledInfoCardValue: { + id: 'judicial.system.core:indictment_overview.scheduled_info_card_value', + defaultMessage: 'Málið er dómtekið', + description: 'Notaður sem gildi á Á dagskrá upplýsingakorti.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx index d47a9bace956..49b05d362a62 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx @@ -12,6 +12,7 @@ import { FormFooter, IndictmentCaseFilesList, IndictmentsLawsBrokenAccordionItem, + InfoCard, InfoCardActiveIndictment, InfoCardCaseScheduledIndictment, InfoCardClosedIndictment, @@ -21,7 +22,10 @@ import { PageTitle, useIndictmentsLawsBroken, } from '@island.is/judicial-system-web/src/components' -import { CaseState } from '@island.is/judicial-system-web/src/graphql/schema' +import { + CaseState, + IndictmentDecision, +} from '@island.is/judicial-system-web/src/graphql/schema' import { useDefendants } from '@island.is/judicial-system-web/src/utils/hooks' import ReturnIndictmentModal from '../ReturnIndictmentCaseModal/ReturnIndictmentCaseModal' @@ -71,6 +75,20 @@ const IndictmentOverview = () => { : formatMessage(strings.inProgressTitle)} + {workingCase.indictmentDecision === + IndictmentDecision.POSTPONING_UNTIL_VERDICT && ( + + + + )} {caseHasBeenReceivedByCourt && workingCase.court && latestDate?.date && ( { - {workingCase.indictmentDecision === - IndictmentDecision.POSTPONING_UNTIL_VERDICT && ( - - - - )} From 048ff3a7692588fd145389b9d5d0d4bddfe91019 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 14:52:52 +0000 Subject: [PATCH 17/42] Save court date when postponing until verdict --- .../Indictments/Conclusion/Conclusion.tsx | 17 ++++- .../Court/Indictments/Overview/Overview.tsx | 62 ++++++++++++------- 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index f596efeb7180..8dd2035c7056 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -34,6 +34,7 @@ import { } from '@island.is/judicial-system-web/src/graphql/schema' import { stepValidationsType } from '@island.is/judicial-system-web/src/utils/formHelper' import { + formatDateForServer, useCase, useS3Upload, useUploadFiles, @@ -50,6 +51,7 @@ type Decision = interface Postponement { postponedIndefinitely?: boolean isSettingVerdictDate?: boolean + verdictDate?: string reason?: string } @@ -139,7 +141,12 @@ const Conclusion: React.FC = () => { [ { indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, - courtDate: null, + courtDate: { + date: + postponement?.isSettingVerdictDate && selectedCourtDate + ? formatDateForServer(selectedCourtDate) + : null, + }, force: true, }, ], @@ -153,7 +160,13 @@ const Conclusion: React.FC = () => { router.push(`${destination}/${workingCase.id}`) }, - [setAndSendCaseToServer, setWorkingCase, workingCase], + [ + postponement?.isSettingVerdictDate, + selectedCourtDate, + setAndSendCaseToServer, + setWorkingCase, + workingCase, + ], ) const handlePostponementIndefinitely = useCallback( diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx index 49b05d362a62..afdee427cb78 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import { useRouter } from 'next/router' -import { Box, toast } from '@island.is/island-ui/core' +import { Box, Text, toast } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' import { core, errors, titles } from '@island.is/judicial-system-web/messages' import { @@ -42,7 +42,6 @@ const IndictmentOverview = () => { 'RETURN_INDICTMENT' | 'SEND_TO_PUBLIC_PROSECUTOR' >() - const caseHasBeenReceivedByCourt = workingCase.state === CaseState.RECEIVED const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate const caseIsClosed = workingCase.state === CaseState.COMPLETED @@ -78,29 +77,46 @@ const IndictmentOverview = () => { {workingCase.indictmentDecision === IndictmentDecision.POSTPONING_UNTIL_VERDICT && ( - - - )} - {caseHasBeenReceivedByCourt && workingCase.court && latestDate?.date && ( - - + {workingCase.courtDate && + workingCase.courtDate.date && + workingCase.court ? ( + + ) : ( + + {formatMessage(strings.scheduledInfoCardValue)} + + ), + }, + ]} + icon="calendar" + /> + )} )} + {workingCase.indictmentDecision === IndictmentDecision.POSTPONING && + workingCase.court && + latestDate && + latestDate.date && ( + + + + )} {caseIsClosed ? ( From 1dc584d6cf2e215f15b8e9a93766522dcac5a3a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 15:23:36 +0000 Subject: [PATCH 18/42] Checkpoint --- .../Court/Indictments/Conclusion/Conclusion.tsx | 16 +++++++++++----- .../web/src/utils/hooks/useSections/index.ts | 10 ++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 55de7e8c0ba2..734ced65563d 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -139,13 +139,18 @@ const Conclusion: React.FC = () => { const success = await setAndSendCaseToServer( [ { - indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, - courtDate: { - date: - postponement?.isSettingVerdictDate && selectedCourtDate + ...(workingCase.indictmentDecision !== + IndictmentDecision.POSTPONING_UNTIL_VERDICT && { + indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, + }), + ...((postponement?.isSettingVerdictDate || + workingCase.courtDate) && { + courtDate: { + date: selectedCourtDate ? formatDateForServer(selectedCourtDate) : null, - }, + }, + }), force: true, }, ], @@ -153,6 +158,7 @@ const Conclusion: React.FC = () => { setWorkingCase, ) + console.log('success', success) if (!success) { return } 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 8d0c0f1363ff..3e1eabe2d2e7 100644 --- a/apps/judicial-system/web/src/utils/hooks/useSections/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useSections/index.ts @@ -25,6 +25,7 @@ import { CaseState, CaseType, Gender, + IndictmentDecision, InstitutionType, User, } from '@island.is/judicial-system-web/src/graphql/schema' @@ -594,7 +595,6 @@ const useSections = ( user?: User, ): RouteSection => { const { id, parentCase, state } = workingCase - return { name: formatMessage(sections.courtSection.title), isActive: @@ -894,7 +894,7 @@ const useSections = ( } const getIndictmentsCourtSections = (workingCase: Case, user?: User) => { - const { id, state } = workingCase + const { id, state, indictmentDecision } = workingCase return { name: formatMessage(sections.indictmentsCourtSection.title), @@ -998,6 +998,12 @@ const useSections = ( href: `${constants.INDICTMENTS_SUMMARY_ROUTE}/${id}`, onClick: !isActive(constants.INDICTMENTS_SUMMARY_ROUTE) && + /** + * This is a special case where we need to check the intent of the judge + * because this last step should only be clicable if the judge intends to + * close the case. + */ + indictmentDecision === IndictmentDecision.COMPLETING && validateFormStepper( isValid, [ From 8649b7b690f4b7a94e647cbb425499b7b32c6b41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 21:32:14 +0000 Subject: [PATCH 19/42] Fixing update functionality --- .../Indictments/Conclusion/Conclusion.tsx | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 734ced65563d..2ef86b0fb672 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -136,29 +136,32 @@ const Conclusion: React.FC = () => { const handlePostponementUntilVerdict = useCallback( async (destination: string) => { - const success = await setAndSendCaseToServer( - [ - { - ...(workingCase.indictmentDecision !== - IndictmentDecision.POSTPONING_UNTIL_VERDICT && { - indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, - }), - ...((postponement?.isSettingVerdictDate || - workingCase.courtDate) && { - courtDate: { - date: selectedCourtDate - ? formatDateForServer(selectedCourtDate) - : null, - }, - }), - force: true, - }, - ], - workingCase, - setWorkingCase, - ) + const updates = { + ...(workingCase.indictmentDecision !== + IndictmentDecision.POSTPONING_UNTIL_VERDICT && { + indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, + }), + ...((postponement?.isSettingVerdictDate || workingCase.courtDate) && { + courtDate: selectedCourtDate + ? { date: formatDateForServer(selectedCourtDate) } + : null, + }), + } + + const success = + Object.keys(updates).length > 0 + ? await setAndSendCaseToServer( + [ + { + ...updates, + force: true, + }, + ], + workingCase, + setWorkingCase, + ) + : true - console.log('success', success) if (!success) { return } From d08c1e37ed6326298db4a3386edd9acb30ef81c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 21:43:10 +0000 Subject: [PATCH 20/42] Fix case state on cases page --- .../web/src/components/TagCaseState/TagCaseState.tsx | 2 +- apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx index bed0100643b6..15de59551ee9 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx @@ -128,8 +128,8 @@ const TagCaseState: FC = (props) => { isValidToDateInThePast, courtDate, isCourtRole, - indictmentDecision, indictmentRulingDecision, + indictmentDecision, ) if (!tagVariant) return null diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql index a60971d44f0e..0bb9915ac204 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql +++ b/apps/judicial-system/web/src/routes/Shared/Cases/cases.graphql @@ -93,5 +93,6 @@ query Cases { indictmentVerdictViewedByAll indictmentVerdictAppealDeadline indictmentRulingDecision + indictmentDecision } } From 4bdf63a22ee81643d5b60ab2041598a30d5dbc88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 21:47:06 +0000 Subject: [PATCH 21/42] Fix migration --- .../migrations/20240528163733-update-case.js | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/apps/judicial-system/backend/migrations/20240528163733-update-case.js b/apps/judicial-system/backend/migrations/20240528163733-update-case.js index 9e2e17c3de56..3182c0ff2c0a 100644 --- a/apps/judicial-system/backend/migrations/20240528163733-update-case.js +++ b/apps/judicial-system/backend/migrations/20240528163733-update-case.js @@ -17,16 +17,9 @@ module.exports = { async down(queryInterface) { return queryInterface.sequelize.transaction((t) => - queryInterface - .removeColumn('case', 'indictment_decision', { - transaction: t, - }) - .then(() => { - queryInterface.sequelize.query( - 'DROP TYPE "enum_case_indictment_decision";', - { transaction: t }, - ) - }), + queryInterface.removeColumn('case', 'indictment_decision', { + transaction: t, + }), ) }, } From 5ec53d6223d8524ef25de7b7994edfe411ce4d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Mon, 3 Jun 2024 22:52:09 +0000 Subject: [PATCH 22/42] Add location to verdict date --- .../Indictments/Conclusion/Conclusion.tsx | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 2ef86b0fb672..bf9d8255ccf8 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -62,7 +62,6 @@ const Conclusion: React.FC = () => { const [selectedAction, setSelectedAction] = useState() const [selectedDecision, setSelectedDecision] = useState() const [postponement, setPostponement] = useState() - const [selectedCourtDate, setSelectedCourtDate] = useState() const { courtDate, @@ -142,8 +141,11 @@ const Conclusion: React.FC = () => { indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, }), ...((postponement?.isSettingVerdictDate || workingCase.courtDate) && { - courtDate: selectedCourtDate - ? { date: formatDateForServer(selectedCourtDate) } + courtDate: courtDate?.date + ? { + date: formatDateForServer(new Date(courtDate.date)), + location: courtDate?.location, + } : null, }), } @@ -170,7 +172,6 @@ const Conclusion: React.FC = () => { }, [ postponement?.isSettingVerdictDate, - selectedCourtDate, setAndSendCaseToServer, setWorkingCase, workingCase, @@ -295,7 +296,7 @@ const Conclusion: React.FC = () => { const stepIsValid = () => { if (selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT) { return postponement?.isSettingVerdictDate - ? Boolean(selectedCourtDate) + ? Boolean(courtDate?.date) : true } else if (!allFilesDoneOrError) { return false @@ -514,14 +515,13 @@ const Conclusion: React.FC = () => { filled /> - { - setSelectedCourtDate(date) - }} + From 9e77317542067b78384d43478b5deaa130fd1510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Tue, 4 Jun 2024 10:41:46 +0000 Subject: [PATCH 23/42] Cleanup --- .../web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index bf9d8255ccf8..271c4a2fd276 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -15,7 +15,6 @@ import { BlueBox, CourtArrangements, CourtCaseInfo, - DateTime, FormContentContainer, FormContext, FormFooter, @@ -171,6 +170,8 @@ const Conclusion: React.FC = () => { router.push(`${destination}/${workingCase.id}`) }, [ + courtDate?.date, + courtDate?.location, postponement?.isSettingVerdictDate, setAndSendCaseToServer, setWorkingCase, From 35feba701680bd11f2b662c6a18e482da04922d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Tue, 4 Jun 2024 14:42:03 +0000 Subject: [PATCH 24/42] Allow judges to remove set court date --- .../CourtArrangements/CourtArrangements.tsx | 22 ++++++++++++---- .../Indictments/Conclusion/Conclusion.tsx | 25 +++++++++++++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx b/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx index 84d7070a9468..32abcfe639b4 100644 --- a/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx +++ b/apps/judicial-system/web/src/components/CourtArrangements/CourtArrangements.tsx @@ -51,7 +51,9 @@ export const useCourtArrangements = ( date: Date | undefined | null, valid = true, ) => { - if (date && valid) { + if (!date) { + setCourtDate(null) + } else if (date && valid) { const oldDate = workingCase[dateKey] if ( oldDate?.date && @@ -72,10 +74,16 @@ export const useCourtArrangements = ( } } - const handleCourtRoomChange = (courtRoom?: string) => { - setCourtDate((previous) => - previous ? { ...previous, location: courtRoom } : { location: courtRoom }, - ) + const handleCourtRoomChange = (courtRoom?: string | null) => { + if (!courtRoom) { + setCourtDate((prev) => ({ ...prev, location: null })) + } else { + setCourtDate((previous) => + previous + ? { ...previous, location: courtRoom } + : { location: courtRoom }, + ) + } } const sendCourtDateToServer = (otherUpdates: UpdateCase[] = []) => { @@ -120,6 +128,10 @@ export const CourtArrangements: React.FC = (props) => { const [courtRoomValue, setCourtRoomValue] = useState('') useEffect(() => { + if (courtDate?.location === null) { + setCourtRoomValue('') + } + if (courtDate?.location) { setCourtRoomValue(courtDate.location) } diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx index 271c4a2fd276..5873f6f42c8a 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Conclusion/Conclusion.tsx @@ -49,7 +49,6 @@ type Decision = interface Postponement { postponedIndefinitely?: boolean isSettingVerdictDate?: boolean - verdictDate?: string reason?: string } @@ -140,12 +139,13 @@ const Conclusion: React.FC = () => { indictmentDecision: IndictmentDecision.POSTPONING_UNTIL_VERDICT, }), ...((postponement?.isSettingVerdictDate || workingCase.courtDate) && { - courtDate: courtDate?.date - ? { - date: formatDateForServer(new Date(courtDate.date)), - location: courtDate?.location, - } - : null, + courtDate: + postponement?.isSettingVerdictDate && courtDate?.date + ? { + date: formatDateForServer(new Date(courtDate.date)), + location: courtDate.location, + } + : null, }), } @@ -292,8 +292,17 @@ const Conclusion: React.FC = () => { workingCase.postponedIndefinitelyExplanation, workingCase.indictmentRulingDecision, workingCase.indictmentDecision, + courtDate, + postponement?.isSettingVerdictDate, ]) + useEffect(() => { + setPostponement((prev) => ({ + ...prev, + isSettingVerdictDate: Boolean(workingCase.courtDate?.date), + })) + }, [workingCase.courtDate?.date]) + const stepIsValid = () => { if (selectedAction === IndictmentDecision.POSTPONING_UNTIL_VERDICT) { return postponement?.isSettingVerdictDate @@ -509,6 +518,8 @@ const Conclusion: React.FC = () => { ...prev, isSettingVerdictDate: !prev?.isSettingVerdictDate, })) + handleCourtDateChange(null) + handleCourtRoomChange(null) }} backgroundColor="white" label={formatMessage(strings.arrangeVerdict)} From fc6b01e2e1b29318332511780d69f9d0deb3d567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=8Dvar=20Oddsson?= Date: Tue, 4 Jun 2024 14:54:21 +0000 Subject: [PATCH 25/42] Added test --- .../TagCaseState/TagCaseState.spec.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts index d742df360bc2..05f720db43d7 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.spec.ts @@ -1,5 +1,9 @@ import { createIntl } from 'react-intl' +import { + CaseIndictmentRulingDecision, + IndictmentDecision, +} from '@island.is/judicial-system/types' import { tables } from '@island.is/judicial-system-web/messages' import { CaseState, @@ -21,6 +25,8 @@ describe('mapCaseStateToTagVariant', () => { caseType: CaseType, isValidToDateInThePast?: boolean, courtDate?: string, + indictmendRulingDecision?: CaseIndictmentRulingDecision | null, + indictmentDecision?: IndictmentDecision | null, ) => mapCaseStateToTagVariant( formatMessage, @@ -29,6 +35,8 @@ describe('mapCaseStateToTagVariant', () => { isValidToDateInThePast, courtDate, isCourtRole, + indictmendRulingDecision, + indictmentDecision, ) test('should return draft state', () => { @@ -118,4 +126,21 @@ describe('mapCaseStateToTagVariant', () => { text: strings.reassignment.defaultMessage, }) }) + + test('should return postponed until verdict state', () => { + expect( + fn( + CaseState.RECEIVED, + false, + CaseType.INDICTMENT, + false, + '2020-01-01', + null, + IndictmentDecision.POSTPONING_UNTIL_VERDICT, + ), + ).toEqual({ + color: 'mint', + text: strings.postponedUntilVerdict.defaultMessage, + }) + }) }) From 47afac6ce855b8a4484fe8511c6d45480a76f03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Wed, 5 Jun 2024 10:47:53 +0000 Subject: [PATCH 26/42] Allows prosecutors to cancel submitted indictments --- .../migrations/20240531113155-update-case.js | 53 ++++++++++++++++++ .../src/app/modules/case/case.controller.ts | 6 ++ .../src/app/modules/case/case.service.ts | 2 +- .../app/modules/case/filters/case.filter.ts | 3 + .../app/modules/case/filters/cases.filter.ts | 4 ++ .../case/filters/test/cases.filter.spec.ts | 5 ++ .../filters/test/defenceUserFilter.spec.ts | 2 + .../test/districtCourtUserFilter.spec.ts | 2 + .../test/prosecutionUserFilter.spec.ts | 4 ++ .../src/app/modules/case/guards/rolesRules.ts | 1 + .../app/modules/case/internalCase.service.ts | 2 +- .../app/modules/case/state/case.state.spec.ts | 56 +++++++++++++++++++ .../src/app/modules/case/state/case.state.ts | 7 +++ .../test/caseController/transition.spec.ts | 2 + .../TagCaseState/TagCaseState.strings.ts | 5 ++ .../components/TagCaseState/TagCaseState.tsx | 5 ++ .../web/src/routes/Defender/Cases/Cases.tsx | 10 +++- .../Indictments/Overview/Overview.strings.ts | 32 ++++++++++- .../Indictments/Overview/Overview.tsx | 40 +++++++++++++ .../web/src/routes/Shared/Cases/Cases.tsx | 32 ++++++++--- libs/judicial-system/types/src/lib/case.ts | 46 ++++++++------- 21 files changed, 287 insertions(+), 32 deletions(-) create mode 100644 apps/judicial-system/backend/migrations/20240531113155-update-case.js diff --git a/apps/judicial-system/backend/migrations/20240531113155-update-case.js b/apps/judicial-system/backend/migrations/20240531113155-update-case.js new file mode 100644 index 000000000000..18fd14e47380 --- /dev/null +++ b/apps/judicial-system/backend/migrations/20240531113155-update-case.js @@ -0,0 +1,53 @@ +'use strict' + +module.exports = { + async up(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction((transaction) => + queryInterface + .changeColumn( + 'case', + 'state', + { type: Sequelize.STRING, allowNull: false }, + { transaction }, + ) + .then(() => + queryInterface.sequelize.query('DROP TYPE "enum_case_state"', { + transaction, + }), + ), + ) + }, + + async down(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction((transaction) => + queryInterface + .changeColumn( + 'case', + 'state', + { + type: Sequelize.ENUM( + 'NEW', + 'DRAFT', + 'WAITING_FOR_CONFIRMATION', + 'SUBMITTED', + 'RECEIVED', + 'MAIN_HEARING', + 'COMPLETED', + 'ACCEPTED', + 'REJECTED', + 'DISMISSED', + 'DELETED', + ), + allowNull: false, + }, + { transaction }, + ) + .then(() => + queryInterface.sequelize.query( + `ALTER TABLE "case" ALTER COLUMN state SET DEFAULT 'NEW'`, + { transaction }, + ), + ), + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index d055e66339fb..8dac765b2028 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -404,6 +404,12 @@ export class CaseController { case CaseTransition.REDISTRIBUTE: update.judgeId = null break + case CaseTransition.ASK_FOR_CANCELLATION: + if (theCase.indictmentDecision) { + throw new ForbiddenException( + `Cannot ask for cancellation of an indictment that is already in progress at the district court`, + ) + } } const updatedCase = await this.caseService.update( diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 408ad4b46bd6..05151c8a9163 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -430,7 +430,7 @@ export class CaseService { ...caseToCreate, state: isIndictmentCase(caseToCreate.type) ? CaseState.DRAFT - : undefined, + : CaseState.NEW, }, { transaction }, ) diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts index ae961f3745f8..b2998ffb9706 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/case.filter.ts @@ -38,6 +38,7 @@ const canProsecutionUserAccessCase = ( CaseState.DRAFT, CaseState.WAITING_FOR_CONFIRMATION, CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.ACCEPTED, @@ -115,6 +116,7 @@ const canDistrictCourtUserAccessCase = (theCase: Case, user: User): boolean => { } else if ( ![ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, @@ -232,6 +234,7 @@ const canDefenceUserAccessCase = (theCase: Case, user: User): boolean => { if ( ![ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.ACCEPTED, diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts index 838275876e6d..3f1c0a9ecdd0 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/cases.filter.ts @@ -32,6 +32,7 @@ const getProsecutionUserCasesQueryFilter = (user: User): WhereOptions => { CaseState.DRAFT, CaseState.WAITING_FOR_CONFIRMATION, CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.ACCEPTED, @@ -99,6 +100,7 @@ const getDistrictCourtUserCasesQueryFilter = (user: User): WhereOptions => { { state: [ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, @@ -129,6 +131,7 @@ const getDistrictCourtUserCasesQueryFilter = (user: User): WhereOptions => { { state: [ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, @@ -243,6 +246,7 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => { { type: indictmentCases }, { state: [ + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts index ae76694aef42..62fe763b7161 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/cases.filter.spec.ts @@ -47,6 +47,7 @@ describe('getCasesQueryFilter', () => { CaseState.DRAFT, CaseState.WAITING_FOR_CONFIRMATION, CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.ACCEPTED, @@ -106,6 +107,7 @@ describe('getCasesQueryFilter', () => { CaseState.DRAFT, CaseState.WAITING_FOR_CONFIRMATION, CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.ACCEPTED, @@ -181,6 +183,7 @@ describe('getCasesQueryFilter', () => { { state: [ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, @@ -228,6 +231,7 @@ describe('getCasesQueryFilter', () => { { state: [ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, CaseState.COMPLETED, @@ -427,6 +431,7 @@ describe('getCasesQueryFilter', () => { { type: indictmentCases }, { state: [ + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, ...completedIndictmentCaseStates, diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/defenceUserFilter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/defenceUserFilter.spec.ts index 927e3ecd7238..a306275a4f0b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/test/defenceUserFilter.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/defenceUserFilter.spec.ts @@ -22,6 +22,7 @@ describe.each(defenceRoles)('defence user %s', (role) => { `r-case type %s`, (type) => { const accessibleCaseStates = [ + CaseState.WAITING_FOR_CANCELLATION, CaseState.SUBMITTED, CaseState.RECEIVED, CaseState.MAIN_HEARING, @@ -171,6 +172,7 @@ describe.each(defenceRoles)('defence user %s', (role) => { describe.each(indictmentCases)(`s-case type %s`, (type) => { const accessibleCaseStates = [ + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, CaseState.MAIN_HEARING, ...completedCaseStates, diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/districtCourtUserFilter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/districtCourtUserFilter.spec.ts index 54588b5af2a2..fe9e21a68855 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/test/districtCourtUserFilter.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/districtCourtUserFilter.spec.ts @@ -39,7 +39,9 @@ const continueFromCaseState = (user: User, type: string, state: string) => { const continueFromIndictmentType = (user: User, type: string) => { const accessibleCaseStates = [ CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, + CaseState.MAIN_HEARING, CaseState.COMPLETED, ] diff --git a/apps/judicial-system/backend/src/app/modules/case/filters/test/prosecutionUserFilter.spec.ts b/apps/judicial-system/backend/src/app/modules/case/filters/test/prosecutionUserFilter.spec.ts index 8a6d1d70de0d..f86f1f6a7199 100644 --- a/apps/judicial-system/backend/src/app/modules/case/filters/test/prosecutionUserFilter.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/filters/test/prosecutionUserFilter.spec.ts @@ -122,11 +122,15 @@ const continueFromType = (user: User, type: CaseType) => { const accessibleCaseStates = [ CaseState.NEW, CaseState.DRAFT, + CaseState.WAITING_FOR_CONFIRMATION, CaseState.SUBMITTED, + CaseState.WAITING_FOR_CANCELLATION, CaseState.RECEIVED, + CaseState.MAIN_HEARING, CaseState.ACCEPTED, CaseState.REJECTED, CaseState.DISMISSED, + CaseState.COMPLETED, ] describe.each( diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts index caf236254b97..623e3cc1c605 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts @@ -189,6 +189,7 @@ export const prosecutorTransitionRule: RolesRule = { CaseTransition.OPEN, CaseTransition.ASK_FOR_CONFIRMATION, CaseTransition.SUBMIT, + CaseTransition.ASK_FOR_CANCELLATION, CaseTransition.DELETE, CaseTransition.APPEAL, CaseTransition.WITHDRAW_APPEAL, diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index 028c0a158b3f..907bdf0c9aa2 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -341,7 +341,7 @@ export class InternalCaseService { ...caseToCreate, state: isIndictmentCase(caseToCreate.type) ? CaseState.DRAFT - : undefined, + : CaseState.NEW, origin: CaseOrigin.LOKE, creatingProsecutorId: creator.id, prosecutorId: diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts index 8114e399c09c..e4eb6b9e1169 100644 --- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts @@ -296,6 +296,62 @@ describe('Transition Case', () => { }, ) + describe.each(indictmentCases)('ask for cancellation %s', (type) => { + const allowedFromStates = [CaseState.SUBMITTED, CaseState.RECEIVED] + + describe.each(allowedFromStates)( + 'state %s - should ask for cancellation', + (fromState) => { + // Act + const res = transitionCase( + CaseTransition.ASK_FOR_CANCELLATION, + type, + fromState, + ) + + // Assert + expect(res).toEqual({ state: CaseState.WAITING_FOR_CANCELLATION }) + }, + ) + + describe.each( + Object.values(CaseState).filter( + (state) => !allowedFromStates.includes(state), + ), + )('state %s - should not ask for cancellation', (fromState) => { + // Arrange + const act = () => + transitionCase(CaseTransition.ASK_FOR_CANCELLATION, type, fromState) + + // Act and assert + expect(act).toThrow(ForbiddenException) + }) + }) + + describe.each([...restrictionCases, ...investigationCases])( + 'ask for cancellation %s', + (type) => { + describe.each(Object.values(CaseState))('state %s', (fromState) => { + it.each([undefined, ...Object.values(CaseAppealState)])( + 'appeal state %s - should not ask for cancellation', + (fromAppealState) => { + // Arrange + const act = () => + transitionCase( + CaseTransition.ASK_FOR_CANCELLATION, + type, + fromState, + fromAppealState, + ) + + // Act and assert + expect(act).toThrow(ForbiddenException) + }, + ) + }) + }, + ) + describe.each(indictmentCases)('receive %s', (type) => { const allowedFromStates = [CaseState.SUBMITTED] diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts index fbae47a66af0..80fa6200ae7a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts +++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts @@ -67,6 +67,13 @@ const indictmentCaseStateMachine: Map< to: { state: IndictmentCaseState.SUBMITTED }, }, ], + [ + IndictmentCaseTransition.ASK_FOR_CANCELLATION, + { + fromStates: [IndictmentCaseState.SUBMITTED, IndictmentCaseState.RECEIVED], + to: { state: IndictmentCaseState.WAITING_FOR_CANCELLATION }, + }, + ], [ IndictmentCaseTransition.RECEIVE, { diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts index 5a3ff83919e4..774152c08b7b 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts @@ -295,6 +295,8 @@ describe('CaseController - Transition', () => { ${CaseTransition.ASK_FOR_CONFIRMATION} | ${CaseState.DRAFT} | ${CaseState.WAITING_FOR_CONFIRMATION} ${CaseTransition.DENY_INDICTMENT} | ${CaseState.WAITING_FOR_CONFIRMATION} | ${CaseState.DRAFT} ${CaseTransition.SUBMIT} | ${CaseState.WAITING_FOR_CONFIRMATION} | ${CaseState.SUBMITTED} + ${CaseTransition.ASK_FOR_CANCELLATION} | ${CaseState.SUBMITTED} | ${CaseState.WAITING_FOR_CANCELLATION} + ${CaseTransition.ASK_FOR_CANCELLATION} | ${CaseState.RECEIVED} | ${CaseState.WAITING_FOR_CANCELLATION} ${CaseTransition.RECEIVE} | ${CaseState.SUBMITTED} | ${CaseState.RECEIVED} ${CaseTransition.RETURN_INDICTMENT} | ${CaseState.RECEIVED} | ${CaseState.DRAFT} ${CaseTransition.REDISTRIBUTE} | ${CaseState.RECEIVED} | ${CaseState.MAIN_HEARING} diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts index 0f472acf678e..f48fe981bc43 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.strings.ts @@ -78,4 +78,9 @@ export const strings = defineMessages({ '{indictmentRulingDecision, select, RULING {Dómur} FINE {Viðurlagaákvörðun} DISMISSAL {Frávísun} CANCELLATION {Niðurfelling} other {Lokið}}', description: 'Notað sem merki þegar mál í stöðu "Dómþulur" í málalista', }, + cancelled: { + id: 'judicial.system.core:tag_case_state.cancelled', + defaultMessage: 'Afturkallað', + description: 'Notað sem merki þegar mál í stöðu "Afturkallað" í málalista', + }, }) diff --git a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx index 15de59551ee9..6ef9eeeaa792 100644 --- a/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx +++ b/apps/judicial-system/web/src/components/TagCaseState/TagCaseState.tsx @@ -99,6 +99,11 @@ export const mapCaseStateToTagVariant = ( color: 'darkerBlue', text: formatMessage(strings.completed, { indictmentRulingDecision }), } + case CaseState.WAITING_FOR_CANCELLATION: + return { + color: 'rose', + text: formatMessage(strings.cancelled), + } default: return { color: 'white', text: formatMessage(strings.unknown) } } diff --git a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx index 3fde1fce30e1..91b69fe242ae 100644 --- a/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Defender/Cases/Cases.tsx @@ -9,6 +9,7 @@ import { PageHeader, SharedPageLayout, } from '@island.is/judicial-system-web/src/components' +import { CaseState } from '@island.is/judicial-system-web/src/graphql/schema' import DefenderCasesTable from './components/DefenderCasesTable' import FilterCheckboxes from './components/FilterCheckboxes' @@ -44,7 +45,14 @@ export const Cases: FC = () => { return [[], []] } - return partition(cases, (c) => !isCompletedCase(c.state)) + return partition( + cases, + (c) => + !( + isCompletedCase(c.state) || + c.state === CaseState.WAITING_FOR_CANCELLATION + ), + ) }, [cases]) const { diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts index 499ba7861d5c..24e17b69be86 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.strings.ts @@ -68,7 +68,6 @@ export const overview = { defaultMessage: 'Hætta við', description: 'Texti í takka í modal glugga á Yfirlit ákæru skefi í ákærum.', }), - indictmentSentToCourt: defineMessage({ id: 'judicial.system.indictments:overview.indictment_sent_to_court', defaultMessage: 'Ákæra hefur verið send dómstól', @@ -99,4 +98,35 @@ export const overview = { description: 'Notaður sem titill á "Ákæra endursend" hluta af yfirliti ákæru á Yfirlit ákæru skefi í ákærum.', }), + askForCancellationButtonText: defineMessage({ + id: 'judicial.system.indictments:overview.ask_for_cancellation_button_text', + defaultMessage: 'Afturkalla ákæru', + description: + 'Texti á takka til að afturkalla ákæru á Yfirlit ákæru skefi í ákærum.', + }), + askForCancellationModalTitle: defineMessage({ + id: 'judicial.system.indictments:overview.ask_for_cancellation_modal_title', + defaultMessage: 'Viltu afturkall ákæru?', + description: + 'Titill í afturkalla modal glugga á Yfirlit ákæru skefi í ákærum.', + }), + askForCancellationModalText: defineMessage({ + id: 'judicial.system.indictments:overview.ask_for_cancellation_modal_text', + defaultMessage: + 'Dómurinn fær tilkynningu um afturköllun ákæru ásamt verjanda hafi verjandi verið skráður.', + description: + 'Texti í afturkalla modal glugga á Yfirlit ákæru skefi í ákærum.', + }), + askForCancellationPrimaryButtonText: defineMessage({ + id: 'judicial.system.indictments:overview.ask_for_cancellation_primary_button_text', + defaultMessage: 'Afturkalla ákæru', + description: + 'Texti í staðfesta takka í afturkalla modal glugga á Yfirlit ákæru skefi í ákærum.', + }), + askForCancellationSecondaryButtonText: defineMessage({ + id: 'judicial.system.indictments:overview.ask_for_cancellation_secondary_button_text', + defaultMessage: 'Hætta við', + description: + 'Texti í hætta við takka í afturkalla modal glugga á Yfirlit ákæru skefi í ákærum.', + }), } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index a06f031d8364..0e1c283cf97c 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -48,6 +48,7 @@ const Overview: React.FC> = () => { | 'caseSubmitModal' | 'caseSentForConfirmationModal' | 'caseDeniedModal' + | 'askForCancellationModal' >('noModal') const [indictmentConfirmationDecision, setIndictmentConfirmationDecision] = useState<'confirm' | 'deny'>() @@ -124,6 +125,18 @@ const Overview: React.FC> = () => { router.push(constants.CASES_ROUTE) } + const handleAskForCancellation = async () => { + const transitionSuccess = await handleTransition( + CaseTransition.ASK_FOR_CANCELLATION, + ) + + if (!transitionSuccess) { + return + } + + router.push(constants.CASES_ROUTE) + } + return ( > = () => { nextIsDisabled={ userCanSendCaseToCourt && !indictmentConfirmationDecision } + actionButtonText={formatMessage(strings.askForCancellationButtonText)} + actionButtonColorScheme={'destructive'} + actionButtonIsDisabled={ + !( + workingCase.state && + [CaseState.SUBMITTED, CaseState.RECEIVED].includes( + workingCase.state, + ) && + !workingCase.indictmentDecision + ) + } + onActionButtonClick={() => setModal('askForCancellationModal')} /> @@ -274,6 +299,21 @@ const Overview: React.FC> = () => { onClose={() => setModal('noModal')} onComplete={() => router.push(constants.CASES_ROUTE)} /> + ) : modal === 'askForCancellationModal' ? ( + setModal('noModal')} + secondaryButtonText={formatMessage( + strings.askForCancellationSecondaryButtonText, + )} + onSecondaryButtonClick={() => setModal('noModal')} + onPrimaryButtonClick={handleAskForCancellation} + primaryButtonText={formatMessage( + strings.askForCancellationPrimaryButtonText, + )} + isPrimaryButtonLoading={isTransitioningCase} + /> ) : null} diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx index 370da48a6d1a..77b07b4edf04 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx @@ -146,7 +146,10 @@ export const Cases: React.FC = () => { ) const casesAwaitingAssignment = filterCases( - (c) => isIndictmentCase(c.type) && c.judge === null, + (c) => + isIndictmentCase(c.type) && + c.state !== CaseState.WAITING_FOR_CANCELLATION && + c.judge === null, ) const casesAwaitingReview = filterCases( @@ -163,11 +166,27 @@ export const Cases: React.FC = () => { return false } - if (isIndictmentCase(c.type) || !isDistrictCourtUser(user)) { - return !isCompletedCase(c.state) - } else { - return !(isCompletedCase(c.state) && c.rulingSignatureDate) + if (isDistrictCourtUser(user)) { + if (isIndictmentCase(c.type)) { + return !isCompletedCase(c.state) + } else { + return !(isCompletedCase(c.state) && c.rulingSignatureDate) + } + } + + if (isProsecutionUser(user)) { + if (isIndictmentCase(c.type)) { + return !( + isCompletedCase(c.state) || + c.state === CaseState.WAITING_FOR_CANCELLATION + ) + } else { + return !isCompletedCase(c.state) + } } + + // This componenet is only used for prosecution and district court users + return false }) const pastCases = filterCases( @@ -200,8 +219,7 @@ export const Cases: React.FC = () => { caseToDelete.state === CaseState.DRAFT || caseToDelete.state === CaseState.WAITING_FOR_CONFIRMATION || caseToDelete.state === CaseState.SUBMITTED || - caseToDelete.state === CaseState.RECEIVED || - caseToDelete.state === CaseState.MAIN_HEARING + caseToDelete.state === CaseState.RECEIVED ) { await transitionCase(caseToDelete.id, CaseTransition.DELETE) refetch() diff --git a/libs/judicial-system/types/src/lib/case.ts b/libs/judicial-system/types/src/lib/case.ts index b530063dcf1f..85d807939542 100644 --- a/libs/judicial-system/types/src/lib/case.ts +++ b/libs/judicial-system/types/src/lib/case.ts @@ -96,6 +96,7 @@ export enum CaseState { SUBMITTED = 'SUBMITTED', RECEIVED = 'RECEIVED', MAIN_HEARING = 'MAIN_HEARING', + WAITING_FOR_CANCELLATION = 'WAITING_FOR_CANCELLATION', COMPLETED = 'COMPLETED', ACCEPTED = 'ACCEPTED', REJECTED = 'REJECTED', @@ -109,6 +110,7 @@ export enum IndictmentCaseState { SUBMITTED = CaseState.SUBMITTED, RECEIVED = CaseState.RECEIVED, MAIN_HEARING = CaseState.MAIN_HEARING, + WAITING_FOR_CANCELLATION = CaseState.WAITING_FOR_CANCELLATION, COMPLETED = CaseState.COMPLETED, DELETED = CaseState.DELETED, } @@ -136,6 +138,7 @@ export enum CaseTransition { ASK_FOR_CONFIRMATION = 'ASK_FOR_CONFIRMATION', DENY_INDICTMENT = 'DENY_INDICTMENT', SUBMIT = 'SUBMIT', + ASK_FOR_CANCELLATION = 'ASK_FOR_CANCELLATION', RECEIVE = 'RECEIVE', RETURN_INDICTMENT = 'RETURN_INDICTMENT', REDISTRIBUTE = 'REDISTRIBUTE', @@ -153,30 +156,31 @@ export enum CaseTransition { } export enum IndictmentCaseTransition { - ASK_FOR_CONFIRMATION = 'ASK_FOR_CONFIRMATION', - DENY_INDICTMENT = 'DENY_INDICTMENT', - SUBMIT = 'SUBMIT', - RECEIVE = 'RECEIVE', - RETURN_INDICTMENT = 'RETURN_INDICTMENT', - REDISTRIBUTE = 'REDISTRIBUTE', - COMPLETE = 'COMPLETE', - DELETE = 'DELETE', + ASK_FOR_CONFIRMATION = CaseTransition.ASK_FOR_CONFIRMATION, + DENY_INDICTMENT = CaseTransition.DENY_INDICTMENT, + SUBMIT = CaseTransition.SUBMIT, + ASK_FOR_CANCELLATION = CaseTransition.ASK_FOR_CANCELLATION, + RECEIVE = CaseTransition.RECEIVE, + RETURN_INDICTMENT = CaseTransition.RETURN_INDICTMENT, + REDISTRIBUTE = CaseTransition.REDISTRIBUTE, + COMPLETE = CaseTransition.COMPLETE, + DELETE = CaseTransition.DELETE, } export enum RequestCaseTransition { - OPEN = 'OPEN', - SUBMIT = 'SUBMIT', - RECEIVE = 'RECEIVE', - ACCEPT = 'ACCEPT', - REJECT = 'REJECT', - DISMISS = 'DISMISS', - DELETE = 'DELETE', - REOPEN = 'REOPEN', - APPEAL = 'APPEAL', - RECEIVE_APPEAL = 'RECEIVE_APPEAL', - COMPLETE_APPEAL = 'COMPLETE_APPEAL', - REOPEN_APPEAL = 'REOPEN_APPEAL', - WITHDRAW_APPEAL = 'WITHDRAW_APPEAL', + OPEN = CaseTransition.OPEN, + SUBMIT = CaseTransition.SUBMIT, + RECEIVE = CaseTransition.RECEIVE, + ACCEPT = CaseTransition.ACCEPT, + REJECT = CaseTransition.REJECT, + DISMISS = CaseTransition.DISMISS, + DELETE = CaseTransition.DELETE, + REOPEN = CaseTransition.REOPEN, + APPEAL = CaseTransition.APPEAL, + RECEIVE_APPEAL = CaseTransition.RECEIVE_APPEAL, + COMPLETE_APPEAL = CaseTransition.COMPLETE_APPEAL, + REOPEN_APPEAL = CaseTransition.REOPEN_APPEAL, + WITHDRAW_APPEAL = CaseTransition.WITHDRAW_APPEAL, } /* eslint-disable @typescript-eslint/naming-convention */ From 74176d65287ec01cb605aef9be1fb85000e72dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Wed, 5 Jun 2024 11:36:18 +0000 Subject: [PATCH 27/42] Hides buttons when prosecutor has asked for cancellation --- .../web/src/components/FormFooter/FormFooter.tsx | 3 ++- .../routes/Prosecutor/Indictments/Overview/Overview.tsx | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx b/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx index 548f9bb4885d..5dc0054a10ed 100644 --- a/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx +++ b/apps/judicial-system/web/src/components/FormFooter/FormFooter.tsx @@ -32,6 +32,7 @@ interface Props { actionButtonColorScheme?: 'destructive' actionButtonIsDisabled?: boolean onActionButtonClick?: () => void + hideActionButton?: boolean infoBoxText?: string } @@ -66,7 +67,7 @@ const FormFooter: React.FC> = (props: Props) => { {!isMobile && (props.previousButtonText || formatMessage(core.back))} - {props.actionButtonText && ( + {!props.hideActionButton && props.actionButtonText && ( - -
- { - setCreateCourtCaseSuccess(false) - removeTabsValidateAndSet( - 'courtCaseNumber', - event.target.value, - [ - 'empty', - isIndictmentCase(workingCase.type) - ? 'S-case-number' - : 'R-case-number', - ], - setWorkingCase, - courtCaseNumberEM, - setCourtCaseNumberEM, - ) - }} - onBlur={(event) => { - validateAndSendToServer( - 'courtCaseNumber', - event.target.value, - [ - 'empty', - isIndictmentCase(workingCase.type) - ? 'S-case-number' - : 'R-case-number', - ], - workingCase, - updateCourtCaseNumber, - setCourtCaseNumberEM, - ) - }} - disabled={ - workingCase.state !== CaseState.SUBMITTED && - workingCase.state !== CaseState.RECEIVED && - workingCase.state !== CaseState.MAIN_HEARING - } - required - /> -
-
- - + ) } diff --git a/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx new file mode 100644 index 000000000000..46b8c3edd1a7 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Court/components/CourtCaseNumber/CourtCaseNumberInput.tsx @@ -0,0 +1,161 @@ +import React, { useState } from 'react' +import { useIntl } from 'react-intl' + +import { Box, Button, Input } from '@island.is/island-ui/core' +import { isIndictmentCase } from '@island.is/judicial-system/types' +import { BlueBox } from '@island.is/judicial-system-web/src/components' +import { CaseState } from '@island.is/judicial-system-web/src/graphql/schema' +import { TempCase as Case } from '@island.is/judicial-system-web/src/types' +import { + removeTabsValidateAndSet, + validateAndSendToServer, +} from '@island.is/judicial-system-web/src/utils/formHelper' +import { + UpdateCase, + useCase, +} from '@island.is/judicial-system-web/src/utils/hooks' +import { validate } from '@island.is/judicial-system-web/src/utils/validate' + +import { courtCaseNumber } from './CourtCaseNumber.strings' +import * as styles from './CourtCaseNumber.css' + +interface Props { + workingCase: Case + setWorkingCase: React.Dispatch> +} + +const CourtCaseNumberInput: React.FC> = ( + props, +) => { + const { workingCase, setWorkingCase } = props + + const { formatMessage } = useIntl() + const { updateCase, createCourtCase, isCreatingCourtCase } = useCase() + const [courtCaseNumberErrorMessage, setCourtCaseNumberErrorMessage] = + useState('') + const [createCourtCaseSuccess, setCreateCourtCaseSuccess] = + useState(false) + + const handleCreateCourtCase = async (workingCase: Case) => { + const courtCaseNumber = await createCourtCase(workingCase, setWorkingCase) + + if (courtCaseNumber !== '') { + setCourtCaseNumberErrorMessage('') + setCreateCourtCaseSuccess(true) + } else { + setCourtCaseNumberErrorMessage( + 'Ekki tókst að stofna nýtt mál, reyndu aftur eða sláðu inn málsnúmer', + ) + } + } + + const updateCourtCaseNumber = async (id: string, update: UpdateCase) => { + const isValid = validate([ + [ + update.courtCaseNumber, + [ + 'empty', + isIndictmentCase(workingCase.type) + ? 'S-case-number' + : 'R-case-number', + ], + ], + ]).isValid + + if (!isValid) { + return + } + + await updateCase(id, update) + } + + return ( + +
+ +
+ +
+
+ { + setCreateCourtCaseSuccess(false) + removeTabsValidateAndSet( + 'courtCaseNumber', + event.target.value, + [ + 'empty', + isIndictmentCase(workingCase.type) + ? 'S-case-number' + : 'R-case-number', + ], + setWorkingCase, + courtCaseNumberErrorMessage, + setCourtCaseNumberErrorMessage, + ) + }} + onBlur={(event) => { + validateAndSendToServer( + 'courtCaseNumber', + event.target.value, + [ + 'empty', + isIndictmentCase(workingCase.type) + ? 'S-case-number' + : 'R-case-number', + ], + workingCase, + updateCourtCaseNumber, + setCourtCaseNumberErrorMessage, + ) + }} + disabled={ + workingCase.state !== CaseState.SUBMITTED && + workingCase.state !== CaseState.WAITING_FOR_CANCELLATION && + workingCase.state !== CaseState.RECEIVED && + workingCase.state !== CaseState.MAIN_HEARING + } + required + /> +
+
+
+
+ ) +} + +export default CourtCaseNumberInput diff --git a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx index 5d0a48490ac5..177b9ee89677 100644 --- a/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/ReceptionAndAssignment/ReceptionAndAssignment.tsx @@ -18,8 +18,6 @@ import { PageLayout, } from '@island.is/judicial-system-web/src/components' import { Gender } from '@island.is/judicial-system-web/src/graphql/schema' -import { TempCase as Case } from '@island.is/judicial-system-web/src/types' -import { useCase } from '@island.is/judicial-system-web/src/utils/hooks' import { getDefendantPleaText } from '@island.is/judicial-system-web/src/utils/stepHelper' import { isReceptionAndAssignmentStepValid } from '@island.is/judicial-system-web/src/utils/validate' @@ -31,27 +29,10 @@ const ReceptionAndAssignment = () => { const router = useRouter() const id = router.query.id const { formatMessage } = useIntl() - const [courtCaseNumberEM, setCourtCaseNumberEM] = useState('') - const [createCourtCaseSuccess, setCreateCourtCaseSuccess] = - useState(false) - const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = + const { workingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) - const { createCourtCase, isCreatingCourtCase } = useCase() - - const handleCreateCourtCase = async (workingCase: Case) => { - const courtCaseNumber = await createCourtCase( - workingCase, - setWorkingCase, - setCourtCaseNumberEM, - ) - - if (courtCaseNumber !== '') { - setCreateCourtCaseSuccess(true) - } - } - const getNextRoute = () => { return isRestrictionCase(workingCase.type) ? constants.RESTRICTION_CASE_COURT_OVERVIEW_ROUTE @@ -137,16 +118,7 @@ const ReceptionAndAssignment = () => { - + diff --git a/apps/judicial-system/web/src/routes/Court/components/index.ts b/apps/judicial-system/web/src/routes/Court/components/index.ts index 5cd387b1bf71..7eb21ae65d86 100644 --- a/apps/judicial-system/web/src/routes/Court/components/index.ts +++ b/apps/judicial-system/web/src/routes/Court/components/index.ts @@ -1,5 +1,6 @@ export { default as AppealSections } from './AppealSections/AppealSections' export { default as CourtCaseNumber } from './CourtCaseNumber/CourtCaseNumber' +export { default as CourtCaseNumberInput } from './CourtCaseNumber/CourtCaseNumberInput' export { default as DraftConclusionModal } from './DraftConclusionModal/DraftConclusionModal' export { default as ReceptionAndAssignment } from './ReceptionAndAssignment/ReceptionAndAssignment' export { default as RulingModifiedModal } from './RulingModifiedModal/RulingModifiedModal' diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx index 3d57cd455f43..fb426c242984 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx @@ -21,6 +21,7 @@ import { import { core, tables } from '@island.is/judicial-system-web/messages' import { ContextMenu, + FormContext, Modal, TagAppealState, TagCaseState, @@ -57,6 +58,7 @@ interface Props { cases: CaseListEntry[] isDeletingCase: boolean onDeleteCase?: (caseToDelete: CaseListEntry) => Promise + onCancelCase?: (caseToCancel: CaseListEntry) => Promise } const ActiveCases: React.FC> = (props) => { @@ -72,12 +74,14 @@ const ActiveCases: React.FC> = (props) => { direction: 'descending', }, ) - const [displayCases, setDisplayCases] = useState([]) - const [modalVisible, setVisibleModal] = useState<'DELETE_CASE'>() - // The index of requset that's about to be removed - const [requestToRemoveIndex, setRequestToRemoveIndex] = useState(-1) const { isOpeningCaseId, showLoading, handleOpenCase, LoadingIndicator } = useCaseList() + const [displayCases, setDisplayCases] = useState([]) + const [modalVisible, setVisibleModal] = useState< + 'DELETE_CASE' | 'CANCEL_CASE' + >() + // The id of the case that's about to be removed + const [caseToRemove, setCaseToRemove] = useState() useEffect(() => { setDisplayCases(cases) @@ -138,7 +142,13 @@ const ActiveCases: React.FC> = (props) => { {displayCases.map((theCase: CaseListEntry) => ( handleOpenCase(theCase.id)} + onClick={() => { + if (c.state === CaseState.WAITING_FOR_CANCELLATION) { + setVisibleModal('CANCEL_CASE') + } else { + handleOpenCase(c.id) + } + }} theCase={theCase} isCourtRole={isDistrictCourtUser(user)} isLoading={isOpeningCaseId === theCase.id && showLoading} @@ -236,7 +246,11 @@ const ActiveCases: React.FC> = (props) => { aria-label="Opna kröfu" aria-disabled={isDeletingCase || isOpeningCaseId === c.id} onClick={() => { - handleOpenCase(c.id) + if (c.state === CaseState.WAITING_FOR_CANCELLATION) { + setVisibleModal('CANCEL_CASE') + } else { + handleOpenCase(c.id) + } }} > @@ -368,52 +382,54 @@ const ActiveCases: React.FC> = (props) => { )} - - {isOpeningCaseId === c.id && showLoading ? ( -
- -
- ) : ( - handleOpenCase(c.id, true), - icon: 'open', - }, - ...(isProsecutionUser(user) && - (isRequestCase(c.type) || - c.state === CaseState.DRAFT || - c.state === CaseState.WAITING_FOR_CONFIRMATION) - ? [ - { - title: formatMessage( - contextMenuStrings.deleteCase, - ), - onClick: () => { - setRequestToRemoveIndex(i) - setVisibleModal('DELETE_CASE') + {c.state !== CaseState.WAITING_FOR_CANCELLATION && ( + + {isOpeningCaseId === c.id && showLoading ? ( +
+ +
+ ) : ( + handleOpenCase(c.id, true), + icon: 'open', + }, + ...(isProsecutionUser(user) && + (isRequestCase(c.type) || + c.state === CaseState.DRAFT || + c.state === CaseState.WAITING_FOR_CONFIRMATION) + ? [ + { + title: formatMessage( + contextMenuStrings.deleteCase, + ), + onClick: () => { + setCaseToRemove(c) + setVisibleModal('DELETE_CASE') + }, + icon: 'trash' as IconMapIcon, }, - icon: 'trash' as IconMapIcon, - }, - ] - : []), - ]} - disclosure={ - { - evt.stopPropagation() - }} - /> - } - /> - )} -
+ ] + : []), + ]} + disclosure={ + { + evt.stopPropagation() + }} + /> + } + /> + )} +
+ )} ))} @@ -426,12 +442,12 @@ const ActiveCases: React.FC> = (props) => { title={formatMessage(m.activeRequests.deleteCaseModal.title)} text={formatMessage(m.activeRequests.deleteCaseModal.text)} onPrimaryButtonClick={async () => { - if (onDeleteCase && requestToRemoveIndex !== -1) { - await onDeleteCase(cases[requestToRemoveIndex]) + if (onDeleteCase && caseToRemove) { + await onDeleteCase(caseToRemove) setDisplayCases((prev) => - prev.filter((c) => c.id !== cases[requestToRemoveIndex].id), + prev.filter((c) => c.id !== caseToRemove.id), ) - setRequestToRemoveIndex(-1) + setCaseToRemove(undefined) setVisibleModal(undefined) } }} @@ -448,6 +464,18 @@ const ActiveCases: React.FC> = (props) => { isPrimaryButtonLoading={isDeletingCase} /> )} + {modalVisible === 'CANCEL_CASE' && ( + { + setVisibleModal(undefined) + }} + /> + )} ) } diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts index 8116e4451042..042749ccc670 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts @@ -108,6 +108,25 @@ export const cases = { description: 'Notaður sem texti á Halda áfram takka í Afturkalla mál.', }, }), + cancelCaseModal: defineMessages({ + title: { + id: 'judicial.system.core:cases.active_requests.cancel_case_modal.title', + defaultMessage: 'Mál afturkallað', + description: 'Notaður sem titill í Afturkalla mál dómstóla modal.', + }, + text: { + id: 'judicial.system.core:cases.active_requests.cancel_case_modal.text', + defaultMessage: + 'Ákæruvaldið hefur afturkallað ákæruna. Hægt er að skrá málsnúmer og ljúka málinu hér.', + description: 'Notaður sem texti í Afturkalla mál dómstóla modal.', + }, + secondaryButtonText: { + id: 'judicial.system.core:cases.active_requests.delete_case_modal.secondary_button_text', + defaultMessage: 'Hætta við', + description: + 'Notaður sem texti á Hætta við takka í Afturkalla mál dómstóla modal.', + }, + }), }, pastRequests: { table: { diff --git a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts index 7724dcc2408f..25e92dd60ca1 100644 --- a/apps/judicial-system/web/src/utils/hooks/useCase/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/useCase/index.ts @@ -209,9 +209,6 @@ const useCase = () => { async ( workingCase: Case, setWorkingCase: React.Dispatch>, - setCourtCaseNumberErrorMessage: React.Dispatch< - React.SetStateAction - >, ): Promise => { try { if (isCreatingCourtCase === false) { @@ -225,16 +222,11 @@ const useCase = () => { courtCaseNumber: (data.createCourtCase as Case).courtCaseNumber, })) - setCourtCaseNumberErrorMessage('') - return data.createCourtCase.courtCaseNumber } } } catch (error) { - // Catch all so we can set an eror message - setCourtCaseNumberErrorMessage( - 'Ekki tókst að stofna nýtt mál, reyndu aftur eða sláðu inn málsnúmer', - ) + // Catch all so we can return the empty string } return '' From cdad57dbf0d295a6706f16029b1e421f26a96577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 7 Jun 2024 16:46:27 +0000 Subject: [PATCH 33/42] Fixes back navigation --- .../src/routes/Prosecutor/Indictments/Overview/Overview.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 6faf5e5898d2..342f703ee25c 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -231,7 +231,8 @@ const Overview: React.FC> = () => { Date: Fri, 7 Jun 2024 16:50:05 +0000 Subject: [PATCH 34/42] Update apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ívar Oddsson --- .../web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 6faf5e5898d2..4bc0478854c0 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -259,7 +259,7 @@ const Overview: React.FC> = () => { workingCase.state === CaseState.WAITING_FOR_CANCELLATION } actionButtonText={formatMessage(strings.askForCancellationButtonText)} - actionButtonColorScheme={'destructive'} + actionButtonColorScheme="destructive" actionButtonIsDisabled={ !( workingCase.state && From c78316058a22db826c6ae6c0d84583221d3501d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 7 Jun 2024 17:20:33 +0000 Subject: [PATCH 35/42] Refactors code --- .../Indictments/Overview/Overview.tsx | 61 +++++++++---------- 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx index 342f703ee25c..7554c44a55e3 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Overview/Overview.tsx @@ -57,16 +57,23 @@ const Overview: React.FC> = () => { const { transitionCase, isTransitioningCase } = useCase() const lawsBroken = useIndictmentsLawsBroken(workingCase) - const isNewIndictment = workingCase.state === CaseState.DRAFT - const isSubmittedIndictment = workingCase.state === CaseState.SUBMITTED - const caseHasBeenReceivedByCourt = + const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate + + const isIndictmentNew = workingCase.state === CaseState.DRAFT + const isIndictmentSubmitted = workingCase.state === CaseState.SUBMITTED + const isIndictmentWaitingForCancellation = + workingCase.state === CaseState.WAITING_FOR_CANCELLATION + const isIndictmentReceived = workingCase.state === CaseState.RECEIVED || workingCase.state === CaseState.MAIN_HEARING - const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate - const userCanSendCaseToCourt = Boolean( - user?.canConfirmIndictment && - workingCase.state === CaseState.WAITING_FOR_CONFIRMATION, - ) + + const userCanSendIndictmentToCourt = + Boolean(user?.canConfirmIndictment) && + workingCase.state === CaseState.WAITING_FOR_CONFIRMATION + const userCanCancelIndictment = + (workingCase.state === CaseState.SUBMITTED || + workingCase.state === CaseState.RECEIVED) && + !workingCase.indictmentDecision const handleTransition = async (transitionType: CaseTransition) => { const caseTransitioned = await transitionCase( @@ -87,15 +94,15 @@ const Overview: React.FC> = () => { let transitionType let modalType: typeof modal = 'noModal' - if (userCanSendCaseToCourt) { + if (userCanSendIndictmentToCourt) { if (indictmentConfirmationDecision === 'confirm') { modalType = 'caseSubmitModal' } else if (indictmentConfirmationDecision === 'deny') { modalType = 'caseDeniedModal' - } else if (isSubmittedIndictment) { + } else if (isIndictmentSubmitted) { transitionType = CaseTransition.ASK_FOR_CONFIRMATION } - } else if (isNewIndictment || isSubmittedIndictment) { + } else if (isIndictmentNew || isIndictmentSubmitted) { transitionType = CaseTransition.ASK_FOR_CONFIRMATION modalType = 'caseSentForConfirmationModal' } else if (workingCase.state === CaseState.WAITING_FOR_CONFIRMATION) { @@ -193,10 +200,10 @@ const Overview: React.FC> = () => {
)} - + - {userCanSendCaseToCourt && ( + {userCanSendIndictmentToCourt && ( > = () => { setModal('askForCancellationModal')} /> From b1309f59395672910833aba834edbb23d34151d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 7 Jun 2024 17:24:35 +0000 Subject: [PATCH 36/42] Rewrites condition --- apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx index 77b07b4edf04..aab52dd2cf4b 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx @@ -149,7 +149,7 @@ export const Cases: React.FC = () => { (c) => isIndictmentCase(c.type) && c.state !== CaseState.WAITING_FOR_CANCELLATION && - c.judge === null, + !c.judge, ) const casesAwaitingReview = filterCases( From 7fcdd3eadb6c3dcd1aeb405311947a1d266eabb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Sun, 9 Jun 2024 17:47:34 +0000 Subject: [PATCH 37/42] Completes cancellation --- .../app/modules/case/state/case.state.spec.ts | 5 +- .../src/app/modules/case/state/case.state.ts | 5 +- .../web/messages/Core/tables.ts | 5 + .../components/Table/CourtDate/CourtDate.tsx | 44 ++++ .../web/src/components/Table/Table.tsx | 57 +++-- .../web/src/components/Table/index.ts | 1 + .../CasesInProgressTable.strings.ts | 42 ++++ .../CasesInProgressTable.tsx | 205 ++++++++++++++++++ .../src/routes/Shared/Cases/ActiveCases.tsx | 136 +++++------- .../src/routes/Shared/Cases/Cases.strings.ts | 19 -- .../web/src/routes/Shared/Cases/Cases.tsx | 68 +++--- .../web/src/utils/hooks/useCaseList/index.tsx | 14 +- 12 files changed, 432 insertions(+), 169 deletions(-) create mode 100644 apps/judicial-system/web/src/components/Table/CourtDate/CourtDate.tsx create mode 100644 apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.strings.ts create mode 100644 apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts index e4eb6b9e1169..59a1ac2adec3 100644 --- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.spec.ts @@ -554,7 +554,10 @@ describe('Transition Case', () => { ) describe.each(indictmentCases)('complete %s', (type) => { - const allowedFromStates = [CaseState.RECEIVED] + const allowedFromStates = [ + CaseState.WAITING_FOR_CANCELLATION, + CaseState.RECEIVED, + ] describe.each(allowedFromStates)( 'state %s - should complete', diff --git a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts index 80fa6200ae7a..9865ba48b1dd 100644 --- a/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts +++ b/apps/judicial-system/backend/src/app/modules/case/state/case.state.ts @@ -98,7 +98,10 @@ const indictmentCaseStateMachine: Map< [ IndictmentCaseTransition.COMPLETE, { - fromStates: [IndictmentCaseState.RECEIVED], + fromStates: [ + IndictmentCaseState.WAITING_FOR_CANCELLATION, + IndictmentCaseState.RECEIVED, + ], to: { state: IndictmentCaseState.COMPLETED }, }, ], diff --git a/apps/judicial-system/web/messages/Core/tables.ts b/apps/judicial-system/web/messages/Core/tables.ts index 9cf5cab7a30f..d6ce53eb22b1 100644 --- a/apps/judicial-system/web/messages/Core/tables.ts +++ b/apps/judicial-system/web/messages/Core/tables.ts @@ -103,4 +103,9 @@ export const tables = defineMessages({ description: 'Notaður sem titill fyrir birtingarstaða dálk í lista yfir mál.', }, + postponed: { + id: 'judicial.system.core:tables.postponed', + defaultMessage: 'Frestað', + description: 'Notaður sem texti þegar mál er frestað.', + }, }) diff --git a/apps/judicial-system/web/src/components/Table/CourtDate/CourtDate.tsx b/apps/judicial-system/web/src/components/Table/CourtDate/CourtDate.tsx new file mode 100644 index 000000000000..aa658d74ff45 --- /dev/null +++ b/apps/judicial-system/web/src/components/Table/CourtDate/CourtDate.tsx @@ -0,0 +1,44 @@ +import { useIntl } from 'react-intl' +import format from 'date-fns/format' +import localeIS from 'date-fns/locale/is' +import parseISO from 'date-fns/parseISO' + +import { Box, Text } from '@island.is/island-ui/core' +import { capitalize } from '@island.is/judicial-system/formatters' +import { tables } from '@island.is/judicial-system-web/messages' + +interface Props { + courtDate?: string | null + postponedIndefinitelyExplanation?: string | null +} + +const CourtDate: React.FC = (props: Props) => { + const { courtDate, postponedIndefinitelyExplanation } = props + const { formatMessage } = useIntl() + + if (!courtDate && !postponedIndefinitelyExplanation) { + return null + } + + return postponedIndefinitelyExplanation ? ( + {formatMessage(tables.postponed)} + ) : ( + courtDate && ( + <> + + + {capitalize( + format(parseISO(courtDate), 'EEEE d. LLLL y', { + locale: localeIS, + }), + ).replace('dagur', 'd.')} + + + + kl. {format(parseISO(courtDate), 'kk:mm')} + + + ) + ) +} +export default CourtDate diff --git a/apps/judicial-system/web/src/components/Table/Table.tsx b/apps/judicial-system/web/src/components/Table/Table.tsx index 027599968ca0..7c5269a99166 100644 --- a/apps/judicial-system/web/src/components/Table/Table.tsx +++ b/apps/judicial-system/web/src/components/Table/Table.tsx @@ -34,6 +34,7 @@ interface TableProps { data: CaseListEntry[] columns: { cell: (row: CaseListEntry) => ReactNode }[] generateContextMenuItems?: (row: CaseListEntry) => ContextMenuItem[] + onClick?: (row: CaseListEntry) => boolean } interface TableWrapperProps { @@ -80,7 +81,7 @@ export const useTable = () => { } const Table: React.FC = (props) => { - const { thead, data, columns, generateContextMenuItems } = props + const { thead, data, columns, generateContextMenuItems, onClick } = props const { isOpeningCaseId, handleOpenCase, LoadingIndicator, showLoading } = useCaseList() const { sortConfig, requestSort, getClassNamesFor } = useTable() @@ -122,7 +123,11 @@ const Table: React.FC = (props) => { {data.map((theCase: CaseListEntry) => ( handleOpenCase(theCase.id)} + onClick={() => { + if (!(onClick && onClick(theCase))) { + handleOpenCase(theCase.id) + } + }} theCase={theCase} isCourtRole={isDistrictCourtUser(user)} isLoading={isOpeningCaseId === theCase.id && showLoading} @@ -185,7 +190,9 @@ const Table: React.FC = (props) => { aria-disabled={isOpeningCaseId === row.id || isTransitioningCase} className={styles.tableRowContainer} onClick={() => { - handleOpenCase(row.id) + if (!(onClick && onClick(row))) { + handleOpenCase(row.id) + } }} > {columns.map((td) => ( @@ -195,27 +202,29 @@ const Table: React.FC = (props) => { ))} {generateContextMenuItems && ( - - {isOpeningCaseId === row.id && showLoading ? ( - - - - ) : ( - { - evt.stopPropagation() - }} - /> - } - /> - )} - + {generateContextMenuItems(row).length > 0 && ( + + {isOpeningCaseId === row.id && showLoading ? ( + + + + ) : ( + { + evt.stopPropagation() + }} + /> + } + /> + )} + + )} )} diff --git a/apps/judicial-system/web/src/components/Table/index.ts b/apps/judicial-system/web/src/components/Table/index.ts index deda840f4234..72f23966d38c 100644 --- a/apps/judicial-system/web/src/components/Table/index.ts +++ b/apps/judicial-system/web/src/components/Table/index.ts @@ -12,3 +12,4 @@ export { export { default as CreatedDate } from './CreatedDate/CreatedDate' export { default as AppealCasesTable } from './AppealCasesTable/AppealCasesTable' export { default as PastCasesTable } from './PastCasesTable/PastCasesTable' +export { default as CourtDate } from './CourtDate/CourtDate' diff --git a/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.strings.ts b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.strings.ts new file mode 100644 index 000000000000..4a7547e2a962 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.strings.ts @@ -0,0 +1,42 @@ +import { defineMessages } from 'react-intl' + +export const strings = defineMessages({ + title: { + id: 'judicial.system.core:court.cases_in_progress.title', + defaultMessage: 'Mál í vinnslu', + description: 'Notaður sem titill í málalista', + }, + noCasesTitle: { + id: 'judicial.system.core:court.cases_in_progress.no_cases_title', + defaultMessage: 'Engin mál í vinnslu.', + description: 'Notaður sem titill þegar engin mál eru til vinnslu', + }, + noCasesMessage: { + id: 'judicial.system.core:court.cases_in_progress.no_cases_message', + defaultMessage: 'Öll mál hafa verið afgreidd.', + description: 'Notað sem skilaboð þegar engin mál eru til vinnslu', + }, + cancelCaseModalTitle: { + id: 'judicial.system.core:cases.active_requests.cancel_case_modal_title', + defaultMessage: 'Mál afturkallað', + description: 'Notaður sem titill í Afturkalla mál dómstóla modal.', + }, + cancelCaseModalText: { + id: 'judicial.system.core:cases.active_requests.cancel_case_modal_text', + defaultMessage: + 'Ákæruvaldið hefur afturkallað ákæruna. Hægt er að skrá málsnúmer og ljúka málinu hér.', + description: 'Notaður sem texti í Afturkalla mál dómstóla modal.', + }, + cancelCaseModalPrimaryButtonText: { + id: 'judicial.system.core:cases.active_requests.cancel_case_modal_primary_button_text', + defaultMessage: 'Ljúka máli', + description: + 'Notaður sem texti á Ljúka máli takka í Afturkalla mál dómstóla modal.', + }, + cancelCaseModalSecondaryButtonText: { + id: 'judicial.system.core:cases.active_requests.delete_case_modal_secondary_button_text', + defaultMessage: 'Hætta við', + description: + 'Notaður sem texti á Hætta við takka í Afturkalla mál dómstóla modal.', + }, +}) diff --git a/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx new file mode 100644 index 000000000000..f9654b20d722 --- /dev/null +++ b/apps/judicial-system/web/src/routes/Court/components/CasesInProgressTable/CasesInProgressTable.tsx @@ -0,0 +1,205 @@ +import React, { useContext, useEffect, useState } from 'react' +import { useIntl } from 'react-intl' +import { AnimatePresence } from 'framer-motion' + +import { Box, toast } from '@island.is/island-ui/core' +import { capitalize } from '@island.is/judicial-system/formatters' +import { CaseIndictmentRulingDecision } from '@island.is/judicial-system/types' +import { core, errors, tables } from '@island.is/judicial-system-web/messages' +import { + FormContext, + Modal, + SectionHeading, + TagCaseState, +} from '@island.is/judicial-system-web/src/components' +import { useContextMenu } from '@island.is/judicial-system-web/src/components/ContextMenu/ContextMenu' +import { + ColumnCaseType, + CourtCaseNumber, + CourtDate, + CreatedDate, + DefendantInfo, +} from '@island.is/judicial-system-web/src/components/Table' +import Table, { + TableWrapper, +} from '@island.is/judicial-system-web/src/components/Table/Table' +import TableInfoContainer from '@island.is/judicial-system-web/src/components/Table/TableInfoContainer/TableInfoContainer' +import { + CaseListEntry, + CaseState, + CaseTransition, +} from '@island.is/judicial-system-web/src/graphql/schema' +import { TempCase as Case } from '@island.is/judicial-system-web/src/types' +import { useCase } from '@island.is/judicial-system-web/src/utils/hooks' + +import CourtCaseNumberInput from '../CourtCaseNumber/CourtCaseNumberInput' +import { strings } from './CasesInProgressTable.strings' + +interface CasesInProgressTableProps { + loading: boolean + isFiltering: boolean + cases: CaseListEntry[] + refetch: () => Promise +} + +const CasesInProgressTable: React.FC = (props) => { + const { loading, isFiltering, cases, refetch } = props + + const { formatMessage } = useIntl() + const { openCaseInNewTabMenuItem } = useContextMenu() + const { getCase } = useContext(FormContext) + const [caseToCancelId, setCaseToCancelId] = useState() + const [caseToCancel, setCaseToCancel] = useState() + const { updateCase, isUpdatingCase, transitionCase, isTransitioningCase } = + useCase() + + useEffect(() => { + if (caseToCancelId) { + getCase(caseToCancelId, setCaseToCancel, () => + toast.error(formatMessage(errors.getCaseToOpen)), + ) + } + }, [caseToCancelId, formatMessage, getCase]) + + const handlePrimaryButtonClick = async () => { + if (!caseToCancelId) { + return + } + + const updated = await updateCase(caseToCancelId, { + indictmentRulingDecision: CaseIndictmentRulingDecision.CANCELLATION, + }) + + if (!updated) { + return + } + + const cancelled = await transitionCase( + caseToCancelId, + CaseTransition.COMPLETE, + ) + + if (!cancelled) { + return + } + + refetch() + + setCaseToCancelId(undefined) + } + + const handleSecondaryButtonClick = () => { + setCaseToCancelId(undefined) + } + + return ( + <> + + + + {cases.length > 0 ? ( + ( + + ), + }, + { + cell: (row) => , + }, + { cell: (row) => }, + { cell: (row) => }, + { + cell: (row) => ( + + ), + }, + { + cell: (row) => ( + + ), + }, + ]} + generateContextMenuItems={(row) => { + return row.state === CaseState.WAITING_FOR_CANCELLATION + ? [] + : [openCaseInNewTabMenuItem(row.id)] + }} + onClick={(row) => { + if (row.state === CaseState.WAITING_FOR_CANCELLATION) { + setCaseToCancelId(row.id) + return true + } else { + return false + } + }} + /> + ) : ( + + )} + + + {caseToCancel && caseToCancel.id === caseToCancelId && ( + + + > + } + /> + + + )} + + ) +} + +export default CasesInProgressTable diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx index fb426c242984..4a5912af93e8 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/ActiveCases.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useMemo, useState } from 'react' +import React, { useEffect, useMemo, useState } from 'react' import { useIntl } from 'react-intl' import { useLocalStorage } from 'react-use' import format from 'date-fns/format' @@ -13,19 +13,13 @@ import { displayFirstPlusRemaining, formatDOB, } from '@island.is/judicial-system/formatters' -import { - isDistrictCourtUser, - isProsecutionUser, - isRequestCase, -} from '@island.is/judicial-system/types' +import { isRequestCase } from '@island.is/judicial-system/types' import { core, tables } from '@island.is/judicial-system-web/messages' import { ContextMenu, - FormContext, Modal, TagAppealState, TagCaseState, - UserContext, } from '@island.is/judicial-system-web/src/components' import { contextMenu as contextMenuStrings } from '@island.is/judicial-system-web/src/components/ContextMenu/ContextMenu.strings' import IconButton from '@island.is/judicial-system-web/src/components/IconButton/IconButton' @@ -58,13 +52,11 @@ interface Props { cases: CaseListEntry[] isDeletingCase: boolean onDeleteCase?: (caseToDelete: CaseListEntry) => Promise - onCancelCase?: (caseToCancel: CaseListEntry) => Promise } const ActiveCases: React.FC> = (props) => { const { cases, isDeletingCase, onDeleteCase } = props - const { user } = useContext(UserContext) const { formatMessage } = useIntl() const { width } = useViewport() const [sortConfig, setSortConfig] = useLocalStorage( @@ -77,9 +69,7 @@ const ActiveCases: React.FC> = (props) => { const { isOpeningCaseId, showLoading, handleOpenCase, LoadingIndicator } = useCaseList() const [displayCases, setDisplayCases] = useState([]) - const [modalVisible, setVisibleModal] = useState< - 'DELETE_CASE' | 'CANCEL_CASE' - >() + const [modalVisible, setVisibleModal] = useState<'DELETE_CASE'>() // The id of the case that's about to be removed const [caseToRemove, setCaseToRemove] = useState() @@ -143,14 +133,10 @@ const ActiveCases: React.FC> = (props) => { { - if (c.state === CaseState.WAITING_FOR_CANCELLATION) { - setVisibleModal('CANCEL_CASE') - } else { - handleOpenCase(c.id) - } + handleOpenCase(theCase.id) }} theCase={theCase} - isCourtRole={isDistrictCourtUser(user)} + isCourtRole={false} isLoading={isOpeningCaseId === theCase.id && showLoading} > {theCase.state && @@ -236,7 +222,7 @@ const ActiveCases: React.FC> = (props) => { - {cases.map((c, i) => ( + {cases.map((c) => ( > = (props) => { aria-label="Opna kröfu" aria-disabled={isDeletingCase || isOpeningCaseId === c.id} onClick={() => { - if (c.state === CaseState.WAITING_FOR_CANCELLATION) { - setVisibleModal('CANCEL_CASE') - } else { - handleOpenCase(c.id) - } + handleOpenCase(c.id) }} > ))} @@ -464,18 +442,6 @@ const ActiveCases: React.FC> = (props) => { isPrimaryButtonLoading={isDeletingCase} /> )} - {modalVisible === 'CANCEL_CASE' && ( - { - setVisibleModal(undefined) - }} - /> - )} ) } diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts index 042749ccc670..8116e4451042 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.strings.ts @@ -108,25 +108,6 @@ export const cases = { description: 'Notaður sem texti á Halda áfram takka í Afturkalla mál.', }, }), - cancelCaseModal: defineMessages({ - title: { - id: 'judicial.system.core:cases.active_requests.cancel_case_modal.title', - defaultMessage: 'Mál afturkallað', - description: 'Notaður sem titill í Afturkalla mál dómstóla modal.', - }, - text: { - id: 'judicial.system.core:cases.active_requests.cancel_case_modal.text', - defaultMessage: - 'Ákæruvaldið hefur afturkallað ákæruna. Hægt er að skrá málsnúmer og ljúka málinu hér.', - description: 'Notaður sem texti í Afturkalla mál dómstóla modal.', - }, - secondaryButtonText: { - id: 'judicial.system.core:cases.active_requests.delete_case_modal.secondary_button_text', - defaultMessage: 'Hætta við', - description: - 'Notaður sem texti á Hætta við takka í Afturkalla mál dómstóla modal.', - }, - }), }, pastRequests: { table: { diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx index aab52dd2cf4b..cde1b2b43836 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.tsx @@ -38,6 +38,7 @@ import { import { useCase } from '@island.is/judicial-system-web/src/utils/hooks' import CasesAwaitingAssignmentTable from '../../Court/components/CasesAwaitingAssignmentTable/CasesAwaitingAssignmentTable' +import CasesInProgressTable from '../../Court/components/CasesInProgressTable/CasesInProgressTable' import CasesAwaitingConfirmationTable from '../../Prosecutor/components/CasesAwaitingConfirmationTable/CasesAwaitingConfirmationTable' import CasesAwaitingReview from '../../PublicProsecutor/Tables/CasesAwaitingReview' import ActiveCases from './ActiveCases' @@ -101,15 +102,13 @@ const CreateCaseButton: React.FC = (props) => { export const Cases: React.FC = () => { const { formatMessage } = useIntl() - - const [isFiltering, setIsFiltering] = useState(false) - const [modalVisible, setVisibleModal] = useState() - const { user } = useContext(UserContext) - const { transitionCase, isTransitioningCase, isSendingNotification } = useCase() + const [isFiltering, setIsFiltering] = useState(false) + const [modalVisible, setVisibleModal] = useState() + const { data, error, loading, refetch } = useCasesQuery({ fetchPolicy: 'no-cache', errorPolicy: 'all', @@ -292,34 +291,47 @@ export const Cases: React.FC = () => { cases={casesAwaitingReview} /> )} + + + {activeCases.length > 0 ? ( + + ) : ( +
+ +
+ )} +
)} - {isDistrictCourtUser(user) && filter.value !== 'INVESTIGATION' && ( - - )} - - - {activeCases.length > 0 ? ( - + + - ) : ( -
- -
- )} -
+ + )} {loading || pastCases.length > 0 ? ( { if (clickedCase[0] !== id && !openInNewTab) { setClickedCase([id, false]) - timeouts.push( - setTimeout(() => { - setClickedCase([id, true]) - }, 2000), - ) + timeouts.push(setTimeout(() => setClickedCase([id, true]), 2000)) } const getCaseToOpen = (id: string) => { getCase( id, - (caseData) => { - openCase(caseData, openInNewTab) - }, - () => { - toast.error(formatMessage(errors.getCaseToOpen)) - }, + (caseData) => openCase(caseData, openInNewTab), + () => toast.error(formatMessage(errors.getCaseToOpen)), ) } From f07c7fcfa931186dfe63480c76878d10a76846b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Mon, 10 Jun 2024 13:36:39 +0000 Subject: [PATCH 38/42] Removes obsolete unit tests --- .../src/routes/Shared/Cases/Cases.spec.tsx | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx index b72884663f11..59e70732318f 100644 --- a/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Cases/Cases.spec.tsx @@ -289,27 +289,6 @@ describe('Cases', () => { }) describe('Court users', () => { - test('should list all cases that do not have status NEW (never returned from the server), DELETED, ACCEPTED or REJECTED in a active cases table', async () => { - render( - - - - - - - , - ) - - expect( - await waitFor( - () => screen.getAllByTestId('custody-cases-table-row').length, - ), - ).toEqual(4) - }) - test('should display the judge logo', async () => { render( { }) }) - describe('Prison users', () => { - test('should list active and past cases in separate tables based on validToDate', async () => { - render( - - - - - - - , - ) - - await waitFor(() => { - expect(screen.getAllByRole('table').length).toEqual(2) - }) - }) - }) - describe('All user types - sorting', () => { test('should order the table data by accused name in ascending order when the user clicks the accused name table header', async () => { const user = userEvent.setup() From ecb6b5aa67283c562d05310bcfb88f0fb26ff11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Mon, 10 Jun 2024 14:53:37 +0000 Subject: [PATCH 39/42] Fixes test helpers --- apps/judicial-system/web/src/utils/testHelpers.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/judicial-system/web/src/utils/testHelpers.tsx b/apps/judicial-system/web/src/utils/testHelpers.tsx index 2ac5b5455299..2c51369653b9 100644 --- a/apps/judicial-system/web/src/utils/testHelpers.tsx +++ b/apps/judicial-system/web/src/utils/testHelpers.tsx @@ -40,6 +40,7 @@ export const FormContextWrapper = ({ caseNotFound: false, isCaseUpToDate: true, refreshCase: jest.fn(), + getCase: jest.fn(), }} > {children} From e17266bd46f58b59004f35c0644c3b9ed5961f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Tue, 11 Jun 2024 17:42:32 +0000 Subject: [PATCH 40/42] Modifies revoked notifications for indictments --- .../src/app/formatters/formatters.spec.ts | 36 +- .../backend/src/app/formatters/formatters.ts | 29 +- .../backend/src/app/messages/notifications.ts | 47 ++- .../src/app/modules/case/case.service.ts | 65 +-- .../test/caseController/transition.spec.ts | 52 ++- .../notification/notification.service.ts | 378 +++++++++++------- .../test/createTestingNotificationModule.ts | 3 - .../sendAppealCompletedNotifications.spec.ts | 11 +- .../sendAppealWithdrawnNotifications.spec.ts | 17 +- .../sendCourtDateNotifications.spec.ts | 31 +- ...antsNotUpdatedAtCourtNotifications.spec.ts | 26 +- .../sendDefenderAssignedNotifications.spec.ts | 26 +- .../sendIndictmentDeniedNotifications.spec.ts | 11 +- ...endIndictmentReturnedNotifications.spec.ts | 11 +- .../sendReadyForCourtNotifications.spec.ts | 67 ++-- .../sendRevokedNotifications.spec.ts | 116 ++++++ .../sendRulingNotifications.spec.ts | 7 - 17 files changed, 552 insertions(+), 381 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRevokedNotifications.spec.ts diff --git a/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts b/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts index 1e481e244591..3379703684ae 100644 --- a/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts +++ b/apps/judicial-system/backend/src/app/formatters/formatters.spec.ts @@ -1666,7 +1666,7 @@ describe('formatDefenderRevokedEmailNotification', () => { // Assert expect(res).toBe( - 'Krafa um gæsluvarðhald sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 000000-1111.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', + 'Krafa um gæsluvarðhald sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 000000-1111.

Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', ) }) @@ -1692,7 +1692,7 @@ describe('formatDefenderRevokedEmailNotification', () => { // Assert expect(res).toBe( - 'Krafa um farbann sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 111100-1111.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', + 'Krafa um farbann sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 111100-1111.

Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', ) }) @@ -1718,7 +1718,7 @@ describe('formatDefenderRevokedEmailNotification', () => { // Assert expect(res).toBe( - 'Krafa um vistun á viðeigandi stofnun sem taka átti fyrir hjá ótilgreindum dómstóli á ótilgreindum tíma, hefur verið afturkölluð.

Sakborningur: Nafn ekki skráð, kt. ekki skráð.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', + 'Krafa um vistun á viðeigandi stofnun sem taka átti fyrir hjá ótilgreindum dómstóli á ótilgreindum tíma, hefur verið afturkölluð.

Sakborningur: Nafn ekki skráð, kt. ekki skráð.

Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', ) }) @@ -1744,7 +1744,7 @@ describe('formatDefenderRevokedEmailNotification', () => { // Assert expect(res).toBe( - 'Krafa um rannsóknarheimild (rof bankaleyndar) sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 111100-1111.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', + 'Krafa um rannsóknarheimild (rof bankaleyndar) sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, kt. 111100-1111.

Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', ) }) @@ -1770,33 +1770,7 @@ describe('formatDefenderRevokedEmailNotification', () => { // Assert expect(res).toBe( - 'Krafa um rannsóknarheimild sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, fd. 01.01.2022.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', - ) - }) - - test('should format revoked notification for indictments', () => { - // Arrange - const type = CaseType.INDICTMENT - const defendantNationalId = '01.01.2022' - const defendantName = 'Gaui Glæpon' - const defendantNoNationalId = true - const court = 'Héraðsdómur Þingvalla' - const courtDate = new Date('2021-01-24T08:15') - - // Act - const res = formatDefenderRevokedEmailNotification( - formatMessage, - type, - defendantNationalId, - defendantName, - defendantNoNationalId, - court, - courtDate, - ) - - // Assert - expect(res).toBe( - 'Ákæra sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, fd. 01.01.2022.

Dómstóllinn hafði skráð þig sem verjanda í málinu.', + 'Krafa um rannsóknarheimild sem taka átti fyrir hjá Héraðsdómi Þingvalla sunnudaginn 24. janúar 2021, kl. 08:15, hefur verið afturkölluð.

Sakborningur: Gaui Glæpon, fd. 01.01.2022.

Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', ) }) }) diff --git a/apps/judicial-system/backend/src/app/formatters/formatters.ts b/apps/judicial-system/backend/src/app/formatters/formatters.ts index 7ac613007d41..e1e4d613f0e2 100644 --- a/apps/judicial-system/backend/src/app/formatters/formatters.ts +++ b/apps/judicial-system/backend/src/app/formatters/formatters.ts @@ -594,22 +594,17 @@ export const formatDefenderRevokedEmailNotification = ( ?.replace(' kl.', ', kl.') : 'NONE', }) - const revokedText = isIndictmentCase(type) - ? formatMessage(cf.revokedIndictment, { - courtText, - courtDateText, - }) - : formatMessage(cf.revoked, { - courtText, - courtDateText, - investigationPrefix: - type === CaseType.OTHER - ? 'onlyPrefix' - : isInvestigationCase(type) - ? 'withPrefix' - : 'noPrefix', - courtTypeName: formatCaseType(type), - }) + const revokedText = formatMessage(cf.revoked, { + courtText, + courtDateText, + investigationPrefix: + type === CaseType.OTHER + ? 'onlyPrefix' + : isInvestigationCase(type) + ? 'withPrefix' + : 'noPrefix', + courtTypeName: formatCaseType(type), + }) const defendantNationalIdText = defendantNoNationalId ? defendantNationalId || 'NONE' : formatNationalId(defendantNationalId || 'NONE') @@ -618,7 +613,7 @@ export const formatDefenderRevokedEmailNotification = ( defendantNationalId: defendantNationalIdText, defendantNoNationalId: defendantNoNationalId ? 'NONE' : 'SOME', }) - const defenderAssignedText = formatMessage(cf.defenderAssignedV2) + const defenderAssignedText = formatMessage(cf.defenderAssigned) return formatMessage(cf.body, { revokedText, diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index af2339b4fc38..c64652bb07d0 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -529,13 +529,6 @@ export const notifications = { description: 'Texti í pósti til verjanda/talsmanns sem tilgreinir að krafa sé afturkölluð', }, - revokedIndictment: { - id: 'judicial.system.backend:notifications.defender_revoked_email.revoked_indictment', - defaultMessage: - 'Ákæra sem taka átti fyrir hjá {courtText} {courtDateText}, hefur verið afturkölluð.', - description: - 'Texti í pósti til verjanda/talsmanns sem tilgreinir að ákæra sé afturkölluð', - }, defendant: { id: 'judicial.system.backend:notifications.defender_revoked_email.defendant', defaultMessage: @@ -543,9 +536,10 @@ export const notifications = { description: 'Texti í pósti til verjanda/talsmanns sem tilgreinir sakborning', }, - defenderAssignedV2: { - id: 'judicial.system.backend:notifications.defender_revoked_email.defender_assigned_v2', - defaultMessage: 'Dómstóllinn hafði skráð þig sem verjanda í málinu.', + defenderAssigned: { + id: 'judicial.system.backend:notifications.defender_revoked_email.defender_assigned_v3', + defaultMessage: + 'Dómstóllinn hafði skráð þig sem verjanda/talsmann í málinu.', description: 'Texti í pósti til verjanda/talsmanns sem tilgreinir að viðkomandi sé skráður verjandi', }, @@ -563,11 +557,17 @@ export const notifications = { description: 'Fyrirsögn í pósti til verjanda/talsmanns þegar krafa er afturkölluð', }, - indictmentSubject: { - id: 'judicial.system.backend:notifications.defender_revoked_email.indictment_subject', - defaultMessage: 'Ákæra afturkölluð', + indictmentBody: { + id: 'judicial.system.backend:notifications.defender_revoked_email.body_indictment', + defaultMessage: + 'Dómstóllinn hafði skráð þig sem verjanda í málinu.

{defenderHasAccessToRvg, select, true {Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast gögn málsins hjá {courtName} ef þau hafa ekki þegar verið afhent}}.', description: - 'Fyrirsögn í pósti til verjanda/talsmanns þegar ákæra er afturkölluð', + 'Notaður sem beinagrind á pósti til verjanda þegar ákæra er afturkölluð', + }, + indictmentSubject: { + id: 'judicial.system.backend:notifications.defender_revoked_email.indictment_subject_v2', + defaultMessage: 'Ákæra afturkölluð í máli {courtCaseNumber}', + description: 'Fyrirsögn í pósti til verjanda þegar ákæra er afturkölluð', }, }), modified: defineMessages({ @@ -586,9 +586,9 @@ export const notifications = { 'Notaður sem texti í tölvupósti vegna breytingar á lengd gæslu/farbanns/vistunar þar sem ekki var úrskurðað í einangrun.', }, htmlDefender: { - id: 'judicial.system.backend:notifications.modified.html_defender', + id: 'judicial.system.backend:notifications.modified.html_defender_v2', defaultMessage: - '{actorInstitution}, {actorName} {actorTitle}, hefur uppfært lengd {caseType, select, ADMISSION_TO_FACILITY {vistunar} TRAVEL_BAN {farbanns} other {gæsluvarðhalds}} í máli {courtCaseNumber}.

{defenderHasAccessToRvg, select, true {Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast gögn málsins hjá {courtName} ef þau hafa ekki þegar verið afhent}}.

Ný lokadagsetning: {validToDate}.', + '{actorInstitution}, {actorName} {actorTitle}, hefur uppfært lengd {caseType, select, ADMISSION_TO_FACILITY {vistunar} TRAVEL_BAN {farbanns} other {gæsluvarðhalds}} í máli {courtCaseNumber}.

{defenderHasAccessToRvg, select, true {Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast gögn málsins hjá {courtName} ef þau hafa ekki þegar verið afhent}}.

Ný lokadagsetning: {validToDate}.', description: 'Notaður sem texti í tölvupósti til verjanda vegna breytingar á lengd gæslu/farbanns/vistunar þar sem ekki var úrskurðað í einangrun.', }, @@ -769,7 +769,6 @@ export const notifications = { description: 'Texti í pósti til aðila máls þegar kæra er afturkölluð', }, }), - emailNames: defineMessages({ prison: { id: 'judicial.system.backend:notifications.email_names.prison', @@ -834,4 +833,18 @@ export const notifications = { description: 'Texti í pósti til sækjanda máls þegar ákæra er endursend', }, }), + courtRevokedIndictmentEmail: defineMessages({ + subject: { + id: 'judicial.system.backend:notifications.court_revoked_indictment_email.subject', + defaultMessage: + 'Ákæra afturkölluð{courtCaseNumber, select, NONE {} other { í máli {courtCaseNumber}}}', + description: 'Fyrirsögn í pósti til dómstóls þegar ákæra er afturkölluð', + }, + body: { + id: 'judicial.system.backend:notifications.court_revoked_indictment_email.body', + defaultMessage: + '{prosecutorsOffice} hefur afturkallað ákæru {courtCaseNumber, select, NONE {í máli sem ekki hefur enn fengið málsnúmer} other {í máli {courtCaseNumber}}}.', + description: 'Texti í pósti til dómstóls þegar ákæra er afturkölluð', + }, + }), } diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index a61fa38030a2..927c2d217441 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -844,12 +844,8 @@ export class CaseService { return this.messageService.sendMessagesToQueue(messages) } - private addMessagesForDeletedCaseToQueue( - theCase: Case, - user: TUser, - previousState: CaseState, - ): Promise { - const messages: CaseMessage[] = [ + private getRevokeMessages(user: TUser, theCase: Case): CaseMessage[] { + return [ { type: MessageType.NOTIFICATION, user, @@ -857,19 +853,33 @@ export class CaseService { body: { type: NotificationType.REVOKED }, }, ] + } - // Indictment cases need some case file cleanup - if (isIndictmentCase(theCase.type)) { - messages.push( - ...this.getIndictmentArchiveMessages( - theCase, - user, - previousState === CaseState.RECEIVED, - ), - ) - } + private addMessagesForDeletedCaseToQueue( + theCase: Case, + user: TUser, + ): Promise { + return this.messageService.sendMessagesToQueue( + this.getRevokeMessages(user, theCase), + ) + } - return this.messageService.sendMessagesToQueue(messages) + private addMessagesForDeletedIndictmentCaseToQueue( + theCase: Case, + user: TUser, + ): Promise { + return this.messageService.sendMessagesToQueue( + this.getIndictmentArchiveMessages(theCase, user, false), + ) + } + + private addMessagesForRevokedIndictmentCaseToQueue( + theCase: Case, + user: TUser, + ): Promise { + return this.messageService.sendMessagesToQueue( + this.getRevokeMessages(user, theCase), + ) } private async addMessagesForAppealedCaseToQueue( @@ -1107,17 +1117,22 @@ export class CaseService { // Only send messages if the case was in a SUBMITTED state - not when reopening a case await this.addMessagesForReceivedCaseToQueue(updatedCase, user) } else if (updatedCase.state === CaseState.DELETED) { - await this.addMessagesForDeletedCaseToQueue( - updatedCase, - user, - theCase.state, - ) - } else if (isCompletedCase(updatedCase.state)) { if (isIndictment) { - await this.addMessagesForCompletedIndictmentCaseToQueue( + await this.addMessagesForDeletedIndictmentCaseToQueue( updatedCase, user, ) + } else { + await this.addMessagesForDeletedCaseToQueue(updatedCase, user) + } + } else if (isCompletedCase(updatedCase.state)) { + if (isIndictment) { + if (theCase.state !== CaseState.WAITING_FOR_CANCELLATION) { + await this.addMessagesForCompletedIndictmentCaseToQueue( + updatedCase, + user, + ) + } } else { await this.addMessagesForCompletedCaseToQueue(updatedCase, user) } @@ -1141,6 +1156,8 @@ export class CaseService { updatedCase, user, ) + } else if (updatedCase.state === CaseState.WAITING_FOR_CANCELLATION) { + await this.addMessagesForRevokedIndictmentCaseToQueue(updatedCase, user) } } diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts index 8109b83623ea..6a2c8a2997a5 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts @@ -431,15 +431,19 @@ describe('CaseController - Transition', () => { } else if (newState === CaseState.DELETED) { expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith( [ - { - type: MessageType.NOTIFICATION, - user: { - ...defaultUser, - canConfirmIndictment: isIndictmentCase(theCase.type), - }, - caseId, - body: { type: NotificationType.REVOKED }, - }, + ...(!isIndictmentCase(theCase.type) + ? [ + { + type: MessageType.NOTIFICATION, + user: { + ...defaultUser, + canConfirmIndictment: isIndictmentCase(theCase.type), + }, + caseId, + body: { type: NotificationType.REVOKED }, + }, + ] + : []), { type: MessageType.ARCHIVING_CASE_FILE, user: { @@ -458,19 +462,6 @@ describe('CaseController - Transition', () => { caseId, elementId: caseFileId2, }, - ...(oldState === CaseState.RECEIVED - ? [ - { - type: MessageType.ARCHIVING_CASE_FILES_RECORD, - user: { - ...defaultUser, - canConfirmIndictment: isIndictmentCase(theCase.type), - }, - caseId, - elementId: policeCaseNumber, - }, - ] - : []), ], ) } else if (newState === CaseState.SUBMITTED) { @@ -563,6 +554,23 @@ describe('CaseController - Transition', () => { }, ], ) + } else if ( + newState === CaseState.WAITING_FOR_CANCELLATION && + isIndictmentCase(theCase.type) + ) { + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith( + [ + { + type: MessageType.NOTIFICATION, + user: { + ...defaultUser, + canConfirmIndictment: isIndictmentCase(theCase.type), + }, + caseId, + body: { type: NotificationType.REVOKED }, + }, + ], + ) } else { expect( mockMessageService.sendMessagesToQueue, diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts index 13e3ee13cc38..4764814ed4cd 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts @@ -46,6 +46,7 @@ import { isIndictmentCase, isInvestigationCase, isProsecutionUser, + isRequestCase, isRestrictionCase, NotificationType, RequestSharedWithDefender, @@ -130,25 +131,26 @@ export class NotificationService { }) } - private async hasSentNotification(caseId: string, type: NotificationType) { - const previousNotifications = await this.notificationModel.findAll({ - where: { caseId, type }, - }) - return previousNotifications.length > 0 + private hasSentNotification( + type: NotificationType, + notifications?: Notification[], + ) { + return notifications?.some((notification) => notification.type === type) } - private async hasReceivedNotification( - caseId: string, - type: NotificationType | NotificationType[], + private hasReceivedNotification( + type?: NotificationType | NotificationType[], address?: string, + notifications?: Notification[], ) { - const previousNotifications = await this.notificationModel.findAll({ - where: { caseId, type }, - }) + const types = type ? [type].flat() : Object.values(NotificationType) - return previousNotifications.some((notification) => { - return notification.recipients.some( - (recipient) => recipient.address === address && recipient.success, + return notifications?.some((notification) => { + return ( + types.includes(notification.type) && + notification.recipients.some( + (recipient) => recipient.address === address && recipient.success, + ) ) }) } @@ -425,7 +427,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ) } @@ -486,7 +488,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ) } @@ -509,10 +511,10 @@ export class NotificationService { this.sendReadyForCourtEmailNotificationToProsecutor(theCase), ] - const courtHasBeenNotified = await this.hasReceivedNotification( - theCase.id, + const courtHasBeenNotified = this.hasReceivedNotification( NotificationType.READY_FOR_COURT, this.getCourtMobileNumbers(theCase.courtId), + theCase.notifications, ) if (!courtHasBeenNotified) { @@ -528,10 +530,10 @@ export class NotificationService { RequestSharedWithDefender.READY_FOR_COURT || theCase.requestSharedWithDefender === RequestSharedWithDefender.COURT_DATE ) { - const hasDefenderBeenNotified = await this.hasReceivedNotification( - theCase.id, + const hasDefenderBeenNotified = this.hasReceivedNotification( [NotificationType.READY_FOR_COURT, NotificationType.COURT_DATE], theCase.defenderEmail, + theCase.notifications, ) if (hasDefenderBeenNotified) { @@ -727,7 +729,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, calendarInvite ? [calendarInvite] : undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ).then((recipient) => { if (recipient.success) { // No need to wait @@ -773,7 +775,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ) } @@ -910,10 +912,10 @@ export class NotificationService { ), ) - const hasDefenderBeenNotified = await this.hasReceivedNotification( - theCase.id, + const hasDefenderBeenNotified = this.hasReceivedNotification( [NotificationType.READY_FOR_COURT], theCase.defenderEmail, + theCase.notifications, ) if (!hasDefenderBeenNotified) { @@ -1181,10 +1183,10 @@ export class NotificationService { (theCase.decision === CaseDecision.REJECTING || theCase.decision === CaseDecision.ACCEPTING_ALTERNATIVE_TRAVEL_BAN) ) { - const prisonHasBeenNotified = await this.hasReceivedNotification( - theCase.id, + const prisonHasBeenNotified = this.hasReceivedNotification( NotificationType.COURT_DATE, this.config.email.prisonEmail, + theCase.notifications, ) if (prisonHasBeenNotified) { @@ -1245,7 +1247,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ) } @@ -1354,24 +1356,6 @@ export class NotificationService { //#endregion //#region REVOKED notifications */ - private async existsRevokableNotification( - caseId: string, - address?: string, - isIndictment?: boolean, - ): Promise { - return this.hasReceivedNotification( - caseId, - isIndictment - ? [NotificationType.DEFENDER_ASSIGNED] - : [ - NotificationType.HEADS_UP, - NotificationType.READY_FOR_COURT, - NotificationType.COURT_DATE, - ], - address, - ) - } - private sendRevokedSmsNotificationToCourt(theCase: Case): Promise { const smsText = formatCourtRevokedSmsNotification( this.formatMessage, @@ -1409,10 +1393,12 @@ export class NotificationService { html, this.formatMessage(notifications.emailNames.prison), this.config.email.prisonEmail, + undefined, + true, ) } - private sendRevokedEmailNotificationToDefender( + private sendRevokedEmailNotificationToDefenderForRequestCase( caseType: CaseType, defendant: Defendant, defenderName?: string, @@ -1420,11 +1406,10 @@ export class NotificationService { arraignmentDate?: Date, courtName?: string, ): Promise { - const subject = isIndictmentCase(caseType) - ? this.formatMessage(notifications.defenderRevokedEmail.indictmentSubject) - : this.formatMessage(notifications.defenderRevokedEmail.subject, { - caseType, - }) + const subject = this.formatMessage( + notifications.defenderRevokedEmail.subject, + { caseType }, + ) const html = formatDefenderRevokedEmailNotification( this.formatMessage, @@ -1436,74 +1421,190 @@ export class NotificationService { arraignmentDate, ) - return this.sendEmail(subject, html, defenderName, defenderEmail) + return this.sendEmail( + subject, + html, + defenderName, + defenderEmail, + undefined, + true, + ) } - private async sendRevokedNotifications( + private sendRevokedEmailNotificationToDefenderForIndictmentCase( + caseId: string, + defenderNationalId?: string, + defenderName?: string, + defenderEmail?: string, + courtName?: string, + courtCaseNumber?: string, + ): Promise { + const subject = this.formatMessage( + notifications.defenderRevokedEmail.indictmentSubject, + { courtCaseNumber }, + ) + + const html = this.formatMessage( + notifications.defenderRevokedEmail.indictmentBody, + { + courtName: courtName?.replace('dómur', 'dómi'), + defenderHasAccessToRvg: Boolean(defenderNationalId), + linkStart: ``, + linkEnd: '', + }, + ) + + return this.sendEmail( + subject, + html, + defenderName, + defenderEmail, + undefined, + !defenderNationalId, + ) + } + + private async sendRevokedNotificationsForRequestCase( theCase: Case, ): Promise { const promises: Promise[] = [] - const arraignmentDate = DateLog.arraignmentDate(theCase.dateLogs)?.date - const courtWasNotified = - !isIndictmentCase(theCase.type) && - (await this.existsRevokableNotification( - theCase.id, - this.getCourtMobileNumbers(theCase.courtId), - )) + const courtWasNotified = this.hasReceivedNotification( + undefined, + this.getCourtMobileNumbers(theCase.courtId), + theCase.notifications, + ) if (courtWasNotified) { promises.push(this.sendRevokedSmsNotificationToCourt(theCase)) } - const prisonWasNotified = - (theCase.type === CaseType.CUSTODY || - theCase.type === CaseType.ADMISSION_TO_FACILITY) && - (await this.existsRevokableNotification( - theCase.id, - this.config.email.prisonEmail, - )) + const prisonWasNotified = this.hasReceivedNotification( + undefined, + this.config.email.prisonEmail, + theCase.notifications, + ) if (prisonWasNotified) { promises.push(this.sendRevokedEmailNotificationToPrison(theCase)) } - if (isIndictmentCase(theCase.type)) { - for (const defendant of theCase.defendants ?? []) { - const defenderWasNotified = await this.existsRevokableNotification( - theCase.id, - defendant.defenderEmail, - isIndictmentCase(theCase.type), + const defenderWasNotified = this.hasReceivedNotification( + undefined, + theCase.defenderEmail, + theCase.notifications, + ) + + if (defenderWasNotified && theCase.defendants) { + const arraignmentDate = DateLog.arraignmentDate(theCase.dateLogs)?.date + + promises.push( + this.sendRevokedEmailNotificationToDefenderForRequestCase( + theCase.type, + theCase.defendants[0], + theCase.defenderName, + theCase.defenderEmail, + arraignmentDate, + theCase.court?.name, + ), + ) + } + + const recipients = await Promise.all(promises) + + if (recipients.length === 0) { + // Nothing to send + return { notificationSent: true } + } + + return this.recordNotification( + theCase.id, + NotificationType.REVOKED, + recipients, + ) + } + + private sendRevokedNotificationToCourt( + theCase: Case, + recipientName?: string, + recipientEmail?: string, + ): Promise { + const subject = this.formatMessage( + notifications.courtRevokedIndictmentEmail.subject, + { + courtCaseNumber: theCase.courtCaseNumber ?? 'NONE', + }, + ) + const body = this.formatMessage( + notifications.courtRevokedIndictmentEmail.body, + { + prosecutorsOffice: theCase.creatingProsecutor?.institution?.name, + courtCaseNumber: theCase.courtCaseNumber ?? 'NONE', + }, + ) + + return this.sendEmail(subject, body, recipientName, recipientEmail) + } + + private async sendRevodeNotificationsForIndictmentCase( + theCase: Case, + ): Promise { + const promises: Promise[] = [] + + if (!theCase.judge && !theCase.registrar) { + promises.push( + this.sendRevokedNotificationToCourt( + theCase, + theCase.court?.name, + this.getCourtEmail(theCase.courtId), + ), + ) + } else { + if (theCase.judge) { + promises.push( + this.sendRevokedNotificationToCourt( + theCase, + theCase.judge.name, + theCase.judge.email, + ), ) + } - if (defenderWasNotified) { - promises.push( - this.sendRevokedEmailNotificationToDefender( - theCase.type, - defendant, - defendant.defenderName, - defendant.defenderEmail, - arraignmentDate, - theCase.court?.name, - ), - ) - } + if (theCase.registrar) { + promises.push( + this.sendRevokedNotificationToCourt( + theCase, + theCase.registrar.name, + theCase.registrar.email, + ), + ) } - } else { - const defenderWasNotified = await this.existsRevokableNotification( - theCase.id, - theCase.defenderEmail, - isIndictmentCase(theCase.type), + } + + const uniqDefendants = _uniqBy( + theCase.defendants ?? [], + (d: Defendant) => d.defenderEmail, + ) + for (const defendant of uniqDefendants) { + const defenderWasNotified = this.hasReceivedNotification( + undefined, + defendant.defenderEmail, + theCase.notifications, ) - if (defenderWasNotified && theCase.defendants) { + + if (defenderWasNotified) { promises.push( - this.sendRevokedEmailNotificationToDefender( - theCase.type, - theCase.defendants[0], - theCase.defenderName, - theCase.defenderEmail, - arraignmentDate, + this.sendRevokedEmailNotificationToDefenderForIndictmentCase( + theCase.id, + defendant.defenderNationalId, + defendant.defenderName, + defendant.defenderEmail, theCase.court?.name, + theCase.courtCaseNumber, ), ) } @@ -1522,21 +1623,31 @@ export class NotificationService { recipients, ) } + + private sendRevokedNotifications( + theCase: Case, + ): Promise { + if (isRequestCase(theCase.type)) { + return this.sendRevokedNotificationsForRequestCase(theCase) + } else { + return this.sendRevodeNotificationsForIndictmentCase(theCase) + } + } //#endregion //#region DEFENDER_ASSIGNED notifications */ - private async shouldSendDefenderAssignedNotification( + private shouldSendDefenderAssignedNotification( theCase: Case, defenderEmail?: string, - ): Promise { + ): boolean { if (!defenderEmail) { return false } if (isIndictmentCase(theCase.type)) { - const hasSentNotificationBefore = await this.hasReceivedNotification( - theCase.id, + const hasSentNotificationBefore = this.hasReceivedNotification( NotificationType.DEFENDER_ASSIGNED, defenderEmail, + theCase.notifications, ) if (hasSentNotificationBefore) { @@ -1552,15 +1663,16 @@ export class NotificationService { if (!isDefenderIncludedInSessionArrangements) return false } else { - const hasDefenderBeenNotified = await this.hasReceivedNotification( - theCase.id, + const hasDefenderBeenNotified = this.hasReceivedNotification( [ NotificationType.READY_FOR_COURT, NotificationType.COURT_DATE, NotificationType.DEFENDER_ASSIGNED, ], theCase.defenderEmail, + theCase.notifications, ) + if (hasDefenderBeenNotified) { return false } @@ -1605,7 +1717,7 @@ export class NotificationService { for (const defendant of uniqDefendants) { const { defenderEmail, defenderNationalId, defenderName } = defendant - const shouldSend = await this.shouldSendDefenderAssignedNotification( + const shouldSend = this.shouldSendDefenderAssignedNotification( theCase, defenderEmail, ) @@ -1622,21 +1734,13 @@ export class NotificationService { } } } else if (DateLog.arraignmentDate(theCase.dateLogs)?.date) { - const shouldSend = await this.shouldSendDefenderAssignedNotification( + const shouldSend = this.shouldSendDefenderAssignedNotification( theCase, theCase.defenderEmail, ) if (shouldSend) { - const recipient = await this.sendCourtDateEmailNotificationToDefender( - theCase, - ) - - return this.recordNotification( - theCase.id, - NotificationType.DEFENDER_ASSIGNED, - [recipient], - ) + promises.push(this.sendCourtDateEmailNotificationToDefender(theCase)) } } @@ -1661,11 +1765,11 @@ export class NotificationService { ): Promise { if ( !theCase.registrar || - (await this.hasReceivedNotification( - theCase.id, + this.hasReceivedNotification( NotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, theCase.registrar?.email, - )) + theCase.notifications, + ) ) { // Nothing to send return { notificationSent: true } @@ -1715,8 +1819,6 @@ export class NotificationService { html, theCase.prosecutor?.name, theCase.prosecutor?.email, - undefined, - true, ) return this.recordNotification( @@ -1749,8 +1851,6 @@ export class NotificationService { html, theCase.prosecutor?.name, theCase.prosecutor?.email, - undefined, - true, ) return this.recordNotification( @@ -1889,7 +1989,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ), ) } @@ -1996,7 +2096,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ), ) } @@ -2130,7 +2230,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ), ) } @@ -2185,14 +2285,7 @@ export class NotificationService { courtOfAppealUsers.forEach((user) => { if (user) { promises.push( - this.sendEmail( - subject, - courtOfAppealHtml, - user.name, - user.email, - undefined, - true, - ), + this.sendEmail(subject, courtOfAppealHtml, user.name, user.email), ) } }) @@ -2237,9 +2330,9 @@ export class NotificationService { private async sendAppealCompletedResultNotifications( theCase: Case, ): Promise { - const isReopened = await this.hasSentNotification( - theCase.id, + const isReopened = this.hasSentNotification( NotificationType.APPEAL_COMPLETED, + theCase.notifications, ) const promises = [] @@ -2338,7 +2431,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ), ) } @@ -2376,7 +2469,7 @@ export class NotificationService { theCase.defenderName, theCase.defenderEmail, undefined, - Boolean(theCase.defenderNationalId) === false, + !theCase.defenderNationalId, ), ) @@ -2427,7 +2520,9 @@ export class NotificationService { courtCaseNumber: theCase.courtCaseNumber, }) - const sendTo = await this.getWithdrawnNotificationRecipients( + // This may result in a defender with no national id getting a link to RVG + // TODO: Separate defenders from other recipients and handle no national id + const sendTo = this.getWithdrawnNotificationRecipients( theCase, user, wasWithdrawnByProsecution, @@ -2440,6 +2535,7 @@ export class NotificationService { }) const recipients = await Promise.all(promises) + return this.recordNotification( theCase.id, NotificationType.APPEAL_WITHDRAWN, @@ -2447,14 +2543,14 @@ export class NotificationService { ) } - private async getWithdrawnNotificationRecipients( + private getWithdrawnNotificationRecipients( theCase: Case, user: User, wasWithdrawnByProsecution: boolean, - ): Promise { - const hasBeenAssigned = await this.hasSentNotification( - theCase.id, + ): RecipientInfo[] { + const hasBeenAssigned = this.hasSentNotification( NotificationType.APPEAL_JUDGES_ASSIGNED, + theCase.notifications, ) const recipients = [ diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts index 0909a33faa6f..11983dbe9cc7 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts @@ -81,9 +81,6 @@ export const createTestingNotificationModule = async () => { provide: getModelToken(Notification), useValue: { create: jest.fn(), - findOne: jest.fn(), - findAll: jest.fn(), - update: jest.fn(), }, }, NotificationService, diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealCompletedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealCompletedNotifications.spec.ts index 4012c218e6d0..88b1e6ab7f73 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealCompletedNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealCompletedNotifications.spec.ts @@ -49,19 +49,12 @@ describe('InternalNotificationController - Send appeal completed notifications', beforeEach(async () => { process.env.COURTS_EMAILS = `{"4676f08b-aab4-4b4f-a366-697540788088":"${courtOfAppealsEmail}"}` - const { - emailService, - notificationConfig, - notificationModel, - internalNotificationController, - } = await createTestingNotificationModule() + const { emailService, notificationConfig, internalNotificationController } = + await createTestingNotificationModule() mockEmailService = emailService mockConfig = notificationConfig - const mockFindAll = notificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) - givenWhenThen = async ( defenderNationalId?: string, appealRulingDecision?: CaseAppealRulingDecision, diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealWithdrawnNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealWithdrawnNotifications.spec.ts index 141d4cc38a4a..1d83fc0d0db8 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealWithdrawnNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealWithdrawnNotifications.spec.ts @@ -23,6 +23,7 @@ interface Then { type GivenWhenThen = ( userRole: UserRole, appealReceivedByCourtDate?: Date, + notifications?: Notification[], ) => Promise describe('InternalNotificationController - Send appeal withdrawn notifications', () => { @@ -60,10 +61,11 @@ describe('InternalNotificationController - Send appeal withdrawn notifications', mockEmailService = emailService mockNotificationModel = notificationModel - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) - - givenWhenThen = async (userRole, appealReceivedByCourtDate) => { + givenWhenThen = async ( + userRole, + appealReceivedByCourtDate, + notifications, + ) => { const then = {} as Then await internalNotificationController @@ -88,6 +90,7 @@ describe('InternalNotificationController - Send appeal withdrawn notifications', judge: { name: judgeName, email: judgeEmail }, appealJudge1: { name: appealJudge1Name, email: appealJudge1Email }, registrar: { name: registrarName, email: registrarEmail }, + notifications, } as Case, { user: { @@ -151,15 +154,13 @@ describe('InternalNotificationController - Send appeal withdrawn notifications', beforeEach(async () => { const mockCreate = mockNotificationModel.create as jest.Mock mockCreate.mockResolvedValueOnce({} as Notification) - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([ + + then = await givenWhenThen(UserRole.PROSECUTOR, receivedDate, [ { caseId, type: NotificationType.APPEAL_JUDGES_ASSIGNED, } as Notification, ]) - - then = await givenWhenThen(UserRole.PROSECUTOR, receivedDate) }) it('should send notification to defender', () => { expect(mockEmailService.sendEmail).toHaveBeenCalledWith( diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts index e1e092ff174e..fc497ddc5de5 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCourtDateNotifications.spec.ts @@ -49,9 +49,6 @@ describe('InternalNotificationController - Send court date notifications', () => mockEmailService = emailService mockNotificationModel = notificationModel - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) - givenWhenThen = async ( theCase: Case, notificationDto: SendInternalNotificationDto, @@ -86,9 +83,6 @@ describe('InternalNotificationController - Send court date notifications', () => } as Case beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([]) - then = await givenWhenThen(theCase, notificationDto) }) @@ -141,16 +135,20 @@ describe('InternalNotificationController - Send court date notifications', () => beforeEach(async () => { const mockCreate = mockNotificationModel.create as jest.Mock mockCreate.mockResolvedValueOnce({} as Notification) - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([ - { - caseId, - type: NotificationType.READY_FOR_COURT, - recipients: [{ address: defenderEmail, success: true }], - } as Notification, - ]) - then = await givenWhenThen(theCase, notificationDto) + then = await givenWhenThen( + { + ...theCase, + notifications: [ + { + caseId, + type: NotificationType.READY_FOR_COURT, + recipients: [{ address: defenderEmail, success: true }], + }, + ], + } as Case, + notificationDto, + ) }) it('should not send link to case to defender', () => { @@ -190,9 +188,6 @@ describe('InternalNotificationController - Send court date notifications', () => } as Case beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([]) - then = await givenWhenThen(theCase, notificationDto) }) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefendantsNotUpdatedAtCourtNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefendantsNotUpdatedAtCourtNotifications.spec.ts index ad2941570d7d..6e98046a7a9b 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefendantsNotUpdatedAtCourtNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefendantsNotUpdatedAtCourtNotifications.spec.ts @@ -9,7 +9,6 @@ import { createTestingNotificationModule } from '../createTestingNotificationMod import { Case } from '../../../case' import { SendInternalNotificationDto } from '../../dto/sendInternalNotification.dto' import { DeliverResponse } from '../../models/deliver.response' -import { Notification } from '../../models/notification.model' interface Then { result: DeliverResponse @@ -39,18 +38,13 @@ describe('InternalNotificationController - Send defendants not updated at court } as Case let mockEmailService: EmailService - let mockNotificationModel: typeof Notification let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { emailService, notificationModel, internalNotificationController } = + const { emailService, internalNotificationController } = await createTestingNotificationModule() mockEmailService = emailService - mockNotificationModel = notificationModel - - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) givenWhenThen = async ( caseId: string, @@ -91,11 +85,19 @@ describe('InternalNotificationController - Send defendants not updated at court let then: Then beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([ - { recipients: [{ address: registrarEmail, success: true }] }, - ]) - then = await givenWhenThen(caseId, theCase, notificationDto) + then = await givenWhenThen( + caseId, + { + ...theCase, + notifications: [ + { + type: NotificationType.DEFENDANTS_NOT_UPDATED_AT_COURT, + recipients: [{ address: registrarEmail, success: true }], + }, + ], + } as Case, + notificationDto, + ) }) it('should not send email', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefenderAssignedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefenderAssignedNotifications.spec.ts index 81dcac2bfade..cdefc359ef4c 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefenderAssignedNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendDefenderAssignedNotifications.spec.ts @@ -56,9 +56,6 @@ describe('InternalNotificationController - Send defender assigned notifications' mockConfig = notificationConfig mockNotificationModel = notificationModel - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) - givenWhenThen = async ( caseId: string, theCase: Case, @@ -241,16 +238,21 @@ describe('InternalNotificationController - Send defender assigned notifications' beforeEach(async () => { const mockCreate = mockNotificationModel.create as jest.Mock mockCreate.mockResolvedValueOnce({} as Notification) - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([ - { - caseId, - type: notificationDto.type, - recipients: [{ address: defendant.defenderEmail, success: true }], - } as Notification, - ]) - then = await givenWhenThen(caseId, theCase, notificationDto) + then = await givenWhenThen( + caseId, + { + ...theCase, + notifications: [ + { + caseId, + type: notificationDto.type, + recipients: [{ address: defendant.defenderEmail, success: true }], + }, + ], + } as Case, + notificationDto, + ) }) it('should return notification was not sent', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentDeniedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentDeniedNotifications.spec.ts index b75d942d4755..6e6463038ad4 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentDeniedNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentDeniedNotifications.spec.ts @@ -13,7 +13,6 @@ import { createTestingNotificationModule } from '../createTestingNotificationMod import { Case } from '../../../case' import { SendInternalNotificationDto } from '../../dto/sendInternalNotification.dto' import { DeliverResponse } from '../../models/deliver.response' -import { Notification } from '../../models/notification.model' jest.mock('../../../../factories') @@ -35,18 +34,13 @@ describe('InternalNotificationController - Send indictment denied notification', const policeCaseNumbers = [uuid(), uuid()] let mockEmailService: EmailService - let mockNotificationModel: typeof Notification let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { emailService, internalNotificationController, notificationModel } = + const { emailService, internalNotificationController } = await createTestingNotificationModule() mockEmailService = emailService - mockNotificationModel = notificationModel - - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) givenWhenThen = async ( theCase: Case, @@ -79,9 +73,6 @@ describe('InternalNotificationController - Send indictment denied notification', } as Case beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([]) - then = await givenWhenThen(theCase, notificationDto) }) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentReturnedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentReturnedNotifications.spec.ts index 86ba67ab84f4..4c5737885fb1 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentReturnedNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendIndictmentReturnedNotifications.spec.ts @@ -13,7 +13,6 @@ import { createTestingNotificationModule } from '../createTestingNotificationMod import { Case } from '../../../case' import { SendInternalNotificationDto } from '../../dto/sendInternalNotification.dto' import { DeliverResponse } from '../../models/deliver.response' -import { Notification } from '../../models/notification.model' jest.mock('../../../../factories') @@ -36,18 +35,13 @@ describe('InternalNotificationController - Send indictment returned notification const courtName = uuid() let mockEmailService: EmailService - let mockNotificationModel: typeof Notification let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { emailService, internalNotificationController, notificationModel } = + const { emailService, internalNotificationController } = await createTestingNotificationModule() mockEmailService = emailService - mockNotificationModel = notificationModel - - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) givenWhenThen = async ( theCase: Case, @@ -81,9 +75,6 @@ describe('InternalNotificationController - Send indictment returned notification } as Case beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([]) - then = await givenWhenThen(theCase, notificationDto) }) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendReadyForCourtNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendReadyForCourtNotifications.spec.ts index 34d47928b101..df061d7eb038 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendReadyForCourtNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendReadyForCourtNotifications.spec.ts @@ -74,7 +74,6 @@ describe('InternalNotificationController - Send ready for court notifications fo let mockEmailService: EmailService let mockSmsService: SmsService let mockNotificationConfig: ConfigType - let mockNotificationModel: typeof Notification let givenWhenThen: GivenWhenThen beforeEach(async () => { @@ -84,17 +83,12 @@ describe('InternalNotificationController - Send ready for court notifications fo emailService, smsService, notificationConfig, - notificationModel, internalNotificationController, } = await createTestingNotificationModule() mockEmailService = emailService mockSmsService = smsService mockNotificationConfig = notificationConfig - mockNotificationModel = notificationModel - - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) givenWhenThen = async (caseId, theCase, notificationDto) => { const then = {} as Then @@ -115,12 +109,6 @@ describe('InternalNotificationController - Send ready for court notifications fo then = await givenWhenThen(caseId, theCase, notificationDto) }) - it('should lookup previous ready for court notifications', () => { - expect(mockNotificationModel.findAll).toHaveBeenCalledWith({ - where: { caseId, type: NotificationType.READY_FOR_COURT }, - }) - }) - it('should send ready for court email notification to prosecutor', () => { expect(mockEmailService.sendEmail).toHaveBeenCalledWith({ from: { @@ -153,21 +141,26 @@ describe('InternalNotificationController - Send ready for court notifications fo describe('subsequent notifications', () => { beforeEach(async () => { - const mockFindOne = mockNotificationModel.findAll as jest.Mock - mockFindOne.mockResolvedValueOnce([ + await givenWhenThen( + caseId, { - caseId, - type: NotificationType.READY_FOR_COURT, - recipients: [ + ...theCase, + notifications: [ { - address: mockNotificationConfig.sms.courtsMobileNumbers[courtId], - success: true, + caseId, + type: NotificationType.READY_FOR_COURT, + recipients: [ + { + address: + mockNotificationConfig.sms.courtsMobileNumbers[courtId], + success: true, + }, + ], }, ], - }, - ]) - - await givenWhenThen(caseId, theCase, notificationDto) + } as Case, + notificationDto, + ) }) it('should send ready for court email notification to prosecutor', () => { @@ -195,15 +188,6 @@ describe('InternalNotificationController - Send ready for court notifications fo ) }) - it('should lookup previous court date notifications', () => { - expect(mockNotificationModel.findAll).toHaveBeenCalledWith({ - where: { - caseId, - type: [NotificationType.READY_FOR_COURT, NotificationType.COURT_DATE], - }, - }) - }) - it('should not send ready for court email notification to defender', () => { expect(mockEmailService.sendEmail).not.toHaveBeenCalledWith( expect.objectContaining({ @@ -234,16 +218,19 @@ describe('InternalNotificationController - Send ready for court notifications fo describe('defender notification', () => { beforeEach(async () => { - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValueOnce([]).mockResolvedValueOnce([ + await givenWhenThen( + caseId, { - recipients: [ - { name: 'Saul Goodman', address: 'saul@dummy.is', success: true }, + ...theCase, + notifications: [ + { + type: NotificationType.READY_FOR_COURT, + recipients: [{ address: 'saul@dummy.is', success: true }], + }, ], - }, - ]) - - await givenWhenThen(caseId, theCase, notificationDto) + } as Case, + notificationDto, + ) }) it('should send ready for court email updated notification to defender', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRevokedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRevokedNotifications.spec.ts new file mode 100644 index 000000000000..2f5d518e82f9 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRevokedNotifications.spec.ts @@ -0,0 +1,116 @@ +import { uuid } from 'uuidv4' + +import { EmailService } from '@island.is/email-service' + +import { NotificationType } from '@island.is/judicial-system/types' + +import { createTestingNotificationModule } from '../createTestingNotificationModule' + +import { Case } from '../../../case' +import { SendInternalNotificationDto } from '../../dto/sendInternalNotification.dto' +import { DeliverResponse } from '../../models/deliver.response' +import { Notification } from '../../models/notification.model' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = (notifications?: Notification[]) => Promise + +describe('InternalNotificationController - Send revoked notifications for indictment cases', () => { + const caseId = uuid() + const judgeName = uuid() + const judgeEmail = uuid() + const registrarName = uuid() + const registrarEmail = uuid() + const defenderNationalId = uuid() + const defenderName = uuid() + const defenderEmail = uuid() + const prosecutorsOfficeName = uuid() + const courtName = uuid() + const courtCaseNumber = uuid() + const theCase = { + id: caseId, + judge: { name: judgeName, email: judgeEmail }, + registrar: { name: registrarName, email: registrarEmail }, + defendants: [{ defenderNationalId, defenderName, defenderEmail }], + creatingProsecutor: { institution: { name: prosecutorsOfficeName } }, + court: { name: courtName }, + courtCaseNumber, + } + + let mockEmailService: EmailService + let mockNotificationModel: typeof Notification + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { emailService, notificationModel, internalNotificationController } = + await createTestingNotificationModule() + + mockEmailService = emailService + mockNotificationModel = notificationModel + + givenWhenThen = async (notifications?: Notification[]) => { + const then = {} as Then + + await internalNotificationController + .sendCaseNotification( + theCase.id, + { ...theCase, notifications } as Case, + { type: NotificationType.REVOKED } as SendInternalNotificationDto, + ) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('notifications sent', () => { + let then: Then + + beforeEach(async () => { + then = await givenWhenThen([ + { + type: NotificationType.COURT_DATE, + recipients: [{ address: defenderEmail, success: true }], + } as Notification, + ]) + }) + + it('should send a notifications', () => { + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ address: judgeEmail, name: judgeName }], + subject: `Ákæra afturkölluð í máli ${courtCaseNumber}`, + html: `${prosecutorsOfficeName} hefur afturkallað ákæru í máli ${courtCaseNumber}. Hægt er að nálgast yfirlitssíðu málsins á rettarvorslugatt.island.is.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ address: registrarEmail, name: registrarName }], + subject: `Ákæra afturkölluð í máli ${courtCaseNumber}`, + html: `${prosecutorsOfficeName} hefur afturkallað ákæru í máli ${courtCaseNumber}. Hægt er að nálgast yfirlitssíðu málsins á rettarvorslugatt.island.is.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ address: defenderEmail, name: defenderName }], + subject: `Ákæra afturkölluð í máli ${courtCaseNumber}`, + html: `Dómstóllinn hafði skráð þig sem verjanda í málinu.

Sjá nánar á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(mockNotificationModel.create).toHaveBeenCalledWith({ + caseId: caseId, + type: NotificationType.REVOKED, + recipients: [ + { address: judgeEmail, success: true }, + { address: registrarEmail, success: true }, + { address: defenderEmail, success: true }, + ], + }) + expect(then.result).toEqual({ delivered: true }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRulingNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRulingNotifications.spec.ts index d8fa2a8618b1..73d76c2e2489 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRulingNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendRulingNotifications.spec.ts @@ -21,7 +21,6 @@ import { Case } from '../../../case' import { Defendant, DefendantService } from '../../../defendant' import { SendInternalNotificationDto } from '../../dto/sendInternalNotification.dto' import { DeliverResponse } from '../../models/deliver.response' -import { Notification } from '../../models/notification.model' import { notificationModuleConfig } from '../../notification.config' jest.mock('../../../../factories') @@ -46,7 +45,6 @@ describe('InternalNotificationController - Send ruling notifications', () => { let mockEmailService: EmailService let mockConfig: ConfigType - let mockNotificationModel: typeof Notification let mockDefendantService: DefendantService let givenWhenThen: GivenWhenThen @@ -56,19 +54,14 @@ describe('InternalNotificationController - Send ruling notifications', () => { const { emailService, notificationConfig, - notificationModel, defendantService, internalNotificationController, } = await createTestingNotificationModule() mockEmailService = emailService mockConfig = notificationConfig - mockNotificationModel = notificationModel mockDefendantService = defendantService - const mockFindAll = mockNotificationModel.findAll as jest.Mock - mockFindAll.mockResolvedValue([]) - givenWhenThen = async (caseId: string, theCase: Case) => { const then = {} as Then From b37c842c5417df1e1d9d7333a83c2837ee508f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Thu, 13 Jun 2024 11:13:48 +0000 Subject: [PATCH 41/42] Handles indictment deletion whith no case files --- .../backend/src/app/modules/case/case.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 927c2d217441..2b07a9d8d0b6 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -864,13 +864,15 @@ export class CaseService { ) } - private addMessagesForDeletedIndictmentCaseToQueue( + private async addMessagesForDeletedIndictmentCaseToQueue( theCase: Case, user: TUser, ): Promise { - return this.messageService.sendMessagesToQueue( - this.getIndictmentArchiveMessages(theCase, user, false), - ) + const messages = this.getIndictmentArchiveMessages(theCase, user, false) + + if (messages.length > 0) { + return this.messageService.sendMessagesToQueue(messages) + } } private addMessagesForRevokedIndictmentCaseToQueue( From e88ab2fb2dd63a177091f525368ca3e7bdcd309a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Wed, 19 Jun 2024 07:48:29 +0000 Subject: [PATCH 42/42] Fixes unit test --- .../app/modules/case/test/caseController/transition.spec.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts index 4e668fcaef75..a1098a85ef53 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/transition.spec.ts @@ -401,7 +401,10 @@ describe('CaseController - Transition', () => { }, ], ) - } else if (newState === CaseState.DELETED) { + } else if ( + newState === CaseState.DELETED && + !isIndictmentCase(theCase.type) + ) { expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith( [ {
@@ -338,7 +320,6 @@ const ActiveCases: React.FC> = (props) => { > = (props) => { )} - {c.state !== CaseState.WAITING_FOR_CANCELLATION && ( - - {isOpeningCaseId === c.id && showLoading ? ( -
- -
- ) : ( - handleOpenCase(c.id, true), - icon: 'open', - }, - ...(isProsecutionUser(user) && - (isRequestCase(c.type) || - c.state === CaseState.DRAFT || - c.state === CaseState.WAITING_FOR_CONFIRMATION) - ? [ - { - title: formatMessage( - contextMenuStrings.deleteCase, - ), - onClick: () => { - setCaseToRemove(c) - setVisibleModal('DELETE_CASE') - }, - icon: 'trash' as IconMapIcon, + + {isOpeningCaseId === c.id && showLoading ? ( +
+ +
+ ) : ( + handleOpenCase(c.id, true), + icon: 'open', + }, + ...(isRequestCase(c.type) || + c.state === CaseState.DRAFT || + c.state === CaseState.WAITING_FOR_CONFIRMATION + ? [ + { + title: formatMessage( + contextMenuStrings.deleteCase, + ), + onClick: () => { + setCaseToRemove(c) + setVisibleModal('DELETE_CASE') }, - ] - : []), - ]} - disclosure={ - { - evt.stopPropagation() - }} - /> - } - /> - )} -
- )} + icon: 'trash' as IconMapIcon, + }, + ] + : []), + ]} + disclosure={ + { + evt.stopPropagation() + }} + /> + } + /> + )} +