Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(j-s): Reviewer completes indictment review #14762

Merged
merged 13 commits into from
May 13, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
CaseAppealRulingDecision,
CaseAppealState,
CaseDecision,
CaseIndictmentRulingDecision,
CaseState,
CaseType,
} from '@island.is/judicial-system/types'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
CaseIndictmentRulingDecision,
CaseLegalProvisions,
CaseType,
IndictmentCaseReviewDecision,
RequestSharedWithDefender,
SessionArrangements,
UserRole,
Expand Down Expand Up @@ -377,4 +378,8 @@ export class UpdateCaseInput {
@Allow()
@Field({ nullable: true })
readonly indictmentReviewerId?: string

@Allow()
@Field(() => IndictmentCaseReviewDecision, { nullable: true })
readonly indictmentReviewDecision?: IndictmentCaseReviewDecision
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CaseState,
CaseType,
CourtDocument,
IndictmentCaseReviewDecision,
RequestSharedWithDefender,
SessionArrangements,
UserRole,
Expand Down Expand Up @@ -48,6 +49,9 @@ registerEnumType(RequestSharedWithDefender, {
registerEnumType(CaseIndictmentRulingDecision, {
name: 'CaseIndictmentRulingDecision',
})
registerEnumType(IndictmentCaseReviewDecision, {
name: 'IndictmentCaseReviewDecision',
})

@ObjectType()
class DateLog {
Expand Down Expand Up @@ -420,6 +424,9 @@ export class Case {
@Field(() => User, { nullable: true })
readonly indictmentReviewer?: User

@Field(() => IndictmentCaseReviewDecision, { nullable: true })
readonly indictmentReviewDecision?: IndictmentCaseReviewDecision

@Field({ nullable: true })
readonly indictmentAppealDeadline?: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use strict'

module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) =>
queryInterface.addColumn(
'case',
'indictment_review_decision',
{
type: Sequelize.STRING,
allowNull: true,
},
{ transaction: t },
),
)
},

down: (queryInterface) => {
return queryInterface.sequelize.transaction((t) =>
queryInterface.removeColumn('case', 'indictment_review_decision', {
transaction: t,
}),
)
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
CaseLegalProvisions,
CaseType,
CourtDocument,
IndictmentCaseReviewDecision,
RequestSharedWithDefender,
SessionArrangements,
UserRole,
Expand Down Expand Up @@ -472,4 +473,9 @@ export class UpdateCaseDto {
@IsUUID()
@ApiPropertyOptional()
readonly indictmentReviewerId?: string

@IsOptional()
@IsEnum(IndictmentCaseReviewDecision)
@ApiPropertyOptional({ enum: IndictmentCaseReviewDecision })
readonly indictmentReviewDecision?: IndictmentCaseReviewDecision
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const canProsecutionUserAccessCase = (
if (
user.institution?.id !== theCase.prosecutorsOfficeId &&
(forUpdate ||
user.institution?.id !== theCase.sharedWithProsecutorsOfficeId)
user.institution?.id !== theCase.sharedWithProsecutorsOfficeId) &&
user.id !== theCase.indictmentReviewerId
) {
return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ const getProsecutionUserCasesQueryFilter = (user: User): WhereOptions => {
[Op.or]: [
{ prosecutors_office_id: user.institution?.id },
{ shared_with_prosecutors_office_id: user.institution?.id },
{ indictment_reviewer_id: user.id },
{
[Op.and]: [
{ indictment_reviewer_id: user.id },
{ indictment_review_decision: null },
],
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ describe('getCasesQueryFilter', () => {
CaseState.COMPLETED,
],
},

{
[Op.or]: [
{ prosecutors_office_id: 'Prosecutors Office Id' },
{ shared_with_prosecutors_office_id: 'Prosecutors Office Id' },
{ indictment_reviewer_id: 'Prosecutor Id' },
{
[Op.and]: [
{ indictment_reviewer_id: 'Prosecutor Id' },
{ indictment_review_decision: null },
],
},
],
},
{
Expand Down Expand Up @@ -117,7 +123,12 @@ describe('getCasesQueryFilter', () => {
[Op.or]: [
{ prosecutors_office_id: 'Prosecutors Office Id' },
{ shared_with_prosecutors_office_id: 'Prosecutors Office Id' },
{ indictment_reviewer_id: 'Prosecutor Id' },
{
[Op.and]: [
{ indictment_reviewer_id: 'Prosecutor Id' },
{ indictment_review_decision: null },
],
},
],
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const prosecutorFields: (keyof UpdateCaseDto)[] = [
'prosecutorStatementDate',
'requestAppealRulingNotToBePublished',
'indictmentDeniedExplanation',
'indictmentReviewDecision',
]

const publicProsecutorFields: (keyof UpdateCaseDto)[] = ['indictmentReviewerId']
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
CaseState,
CaseType,
CourtDocument,
IndictmentCaseReviewDecision,
RequestSharedWithDefender,
SessionArrangements,
UserRole,
Expand Down Expand Up @@ -1252,4 +1253,15 @@ export class Case extends Model {
@BelongsTo(() => User, 'indictmentReviewerId')
@ApiPropertyOptional({ type: User })
indictmentReviewer?: User

/**********
* The review decision in indictment cases
**********/
@Column({
type: DataType.ENUM,
allowNull: true,
values: Object.values(IndictmentCaseReviewDecision),
})
@ApiPropertyOptional({ enum: IndictmentCaseReviewDecision })
indictmentReviewDecision?: IndictmentCaseReviewDecision
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,12 +111,12 @@ const Conclusion: React.FC = () => {
} else if (selectedAction === 'COMPLETE') {
handleCompletion()
} else if (postponement?.postponedIndefinitely) {
const updateSuccss = await updateCase(workingCase.id, {
const updateSuccess = await updateCase(workingCase.id, {
courtDate: null,
postponedIndefinitelyExplanation: postponement.reason,
})

if (!updateSuccss) {
if (!updateSuccess) {
return
}
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { style } from '@vanilla-extract/css'

import { theme } from '@island.is/island-ui/theme'

export const gridRow = style({
display: 'grid',
gridTemplateColumns: '1.6fr 1fr',
gridGap: theme.spacing[1],
marginBottom: theme.spacing[1],
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { defineMessages } from 'react-intl'

export const strings = defineMessages({
title: {
id: 'judicial.system.core:public_prosecutor.indictments.review_decision.title',
defaultMessage: 'Ákvörðun um áfrýjun',
description: 'Notaður sem titill á ákvörðum um áfrýjun boxi fyrir ákæru.',
},
subtitle: {
id: 'judicial.system.core:public_prosecutor.indictments.review_decision.subtitle',
defaultMessage:
'Frestur til að áfrýja dómi rennur út {indictmentAppealDeadline}',
description:
'Notaður sem undirtitill á ákvörðum um áfrýjun boxi fyrir ákæru.',
},
appealToCourtOfAppeals: {
id: 'judicial.system.core:public_prosecutor.indictments.review_decision.appeal_to_court_of_appeals',
defaultMessage: 'Áfrýja héraðsdómi til Landsréttar',
description:
'Notaður sem texti fyrir "Áfrýja héraðsdómi til Landsréttar" radio takka.',
},
acceptDecision: {
id: 'judicial.system.core:public_prosecutor.indictments.review_decision.accept_decision',
defaultMessage: 'Una héraðsdómi',
description: 'Notaður sem texti fyrir "Una héraðsdómi" radio takka.',
},
reviewModalTitle: {
id: 'judicial.system.core:indictments_review.title',
defaultMessage: 'Staðfesta ákvörðun',
description: 'Notaður sem titill á yfirliti ákæru.',
},
reviewModalText: {
id: 'judicial.system.core:indictments_review.modal_text',

defaultMessage:
'Ertu viss um að þú viljir {reviewerDecision, select, ACCEPT {una héraðsdómi} APPEAL {áfrýja héraðsdómi til Landsréttar} other {halda áfram}}?',
description: 'Notaður sem texti í yfirlitsglugga um yfirlit ákæru.',
},
reviewModalPrimaryButtonText: {
id: 'judicial.system.core:indictments_review.modal_primary_button_text',
defaultMessage: 'Staðfesta',
description:
'Notaður sem texti á aðal takka í yfirlitsglugga um yfirlit ákæru.',
},
reviewModalSecondaryButtonText: {
id: 'judicial.system.core:indictments_review.modal_secondary_button_text',
defaultMessage: 'Hætta við',
description:
'Notaður sem texti á aukatakka í yfirlitsglugga um yfirlit ákæru.',
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useContext, useState } from 'react'
import { useIntl } from 'react-intl'
import { useRouter } from 'next/router'

import { Box, RadioButton, Text } from '@island.is/island-ui/core'
import * as constants from '@island.is/judicial-system/consts'
import { formatDate } from '@island.is/judicial-system/formatters'
import {
IndictmentCaseReviewDecision,
isPublicProsecutor,
} from '@island.is/judicial-system/types'
import {
BlueBox,
Modal,
SectionHeading,
UserContext,
} from '@island.is/judicial-system-web/src/components'
import { useCase } from '@island.is/judicial-system-web/src/utils/hooks'

import { strings } from './ReviewDecision.strings'
import * as styles from './ReviewDecision.css'

interface Props {
caseId: string
indictmentAppealDeadline?: string
modalVisible?: boolean
setModalVisible: React.Dispatch<React.SetStateAction<boolean>>
onSelect?: () => void
}

export const ReviewDecision: React.FC<Props> = (props) => {
const { user } = useContext(UserContext)
const router = useRouter()
const { formatMessage: fm } = useIntl()
const { updateCase } = useCase()

const {
caseId,
indictmentAppealDeadline,
modalVisible,
setModalVisible,
onSelect,
} = props

const [indictmentReviewDecision, setIndictmentReviewDecision] = useState<
IndictmentCaseReviewDecision | undefined
>(undefined)

const handleReviewDecision = async () => {
if (!indictmentReviewDecision) {
return
}
const updateSuccess = await updateCase(caseId, {
indictmentReviewDecision: indictmentReviewDecision,
})
if (updateSuccess) {
router.push(constants.CASES_ROUTE)
}
}

const options = [
{
label: fm(strings.appealToCourtOfAppeals),
value: IndictmentCaseReviewDecision.APPEAL,
},
{
label: fm(strings.acceptDecision),
value: IndictmentCaseReviewDecision.ACCEPT,
},
]

if (!isPublicProsecutor(user)) {
return null
}

return (
<Box marginBottom={5}>
<SectionHeading
title={fm(strings.title)}
description={
<Text variant="eyebrow">
{fm(strings.subtitle, {
indictmentAppealDeadline: formatDate(
indictmentAppealDeadline,
'P',
),
})}
</Text>
}
/>
<BlueBox>
<div className={styles.gridRow}>
{options.map((item, index) => {
return (
<RadioButton
name={`reviewOption-${index}`}
label={item.label}
value={item.value}
checked={indictmentReviewDecision === item.value}
onChange={() => {
onSelect && onSelect()
setIndictmentReviewDecision(item.value)
}}
backgroundColor="white"
large
/>
)
})}
</div>
</BlueBox>
{modalVisible && (
<Modal
title={fm(strings.reviewModalTitle)}
text={fm(strings.reviewModalText, {
reviewerDecision: indictmentReviewDecision,
})}
primaryButtonText={fm(strings.reviewModalPrimaryButtonText)}
secondaryButtonText={fm(strings.reviewModalSecondaryButtonText)}
onClose={() => setModalVisible(false)}
onPrimaryButtonClick={handleReviewDecision}
onSecondaryButtonClick={() => setModalVisible(false)}
/>
)}
</Box>
)
}
Loading
Loading