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): Add received date and new table state tag for indictment prison cases #17279

Merged
merged 15 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ export class Defendant {
@Field(() => String, { nullable: true })
readonly sentToPrisonAdminDate?: string

@Field(() => String, { nullable: true })
readonly openedByPrisonAdminDate?: string

@Field(() => PunishmentType, { nullable: true })
readonly punishmentType?: PunishmentType
}
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,16 @@ export const caseListInclude: Includeable[] = [
as: 'defendants',
required: false,
order: [['created', 'ASC']],
include: [
{
model: DefendantEventLog,
as: 'eventLogs',
required: false,
where: { eventType: defendantEventTypes },
order: [['created', 'DESC']],
separate: true,
},
],
separate: true,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
NestInterceptor,
} from '@nestjs/common'

import { DefendantEventType } from '@island.is/judicial-system/types'

import { Defendant, DefendantEventLog } from '../../defendant'
import { Case } from '../models/case.model'
import { CaseString } from '../models/caseString.model'
Expand All @@ -15,8 +17,15 @@ export const transformDefendants = (defendants?: Defendant[]) => {
return defendants?.map((defendant) => ({
...defendant.toJSON(),
sentToPrisonAdminDate: defendant.isSentToPrisonAdmin
? DefendantEventLog.sentToPrisonAdminDate(defendant.eventLogs)?.created
? DefendantEventLog.getDefendantEventLogTypeDate({
defendantEventLogs: defendant.eventLogs,
eventType: DefendantEventType.SENT_TO_PRISON_ADMIN,
})
: undefined,
openedByPrisonAdminDate: DefendantEventLog.getDefendantEventLogTypeDate({
defendantEventLogs: defendant.eventLogs,
eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN,
}),
}))
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common'

import {
DefendantEventType,
isIndictmentCase,
isPrisonAdminUser,
User,
} from '@island.is/judicial-system/types'

import { DefendantEventLog, DefendantService } from '../../defendant'
import { Case } from '../models/case.model'

const hasValidOpenByPrisonAdminEvent = (
gudjong marked this conversation as resolved.
Show resolved Hide resolved
defendantEventLogs: DefendantEventLog[],
) => {
const sentToPrisonAdminDate = DefendantEventLog.getDefendantEventLogTypeDate({
defendantEventLogs,
eventType: DefendantEventType.SENT_TO_PRISON_ADMIN,
})
const openedByPrisonAdminDate =
DefendantEventLog.getDefendantEventLogTypeDate({
defendantEventLogs,
eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN,
})
return (
sentToPrisonAdminDate &&
openedByPrisonAdminDate &&
sentToPrisonAdminDate <= openedByPrisonAdminDate
)
}

@Injectable()
export class DefendantIndictmentAccessedInterceptor implements NestInterceptor {
unakb marked this conversation as resolved.
Show resolved Hide resolved
constructor(private readonly defendantService: DefendantService) {}

intercept(context: ExecutionContext, next: CallHandler) {
const request = context.switchToHttp().getRequest()
const user: User = request.user
const theCase: Case = request.case

if (isIndictmentCase(theCase.type) && isPrisonAdminUser(user)) {
const defendantsIndictmentNotOpened = theCase.defendants?.filter(
({ isSentToPrisonAdmin, eventLogs = [] }) =>
isSentToPrisonAdmin && !hasValidOpenByPrisonAdminEvent(eventLogs),
)

// create new events for all defendants that prison admin has not accessed according to defendant event logs
defendantsIndictmentNotOpened?.forEach((defendant) =>
this.defendantService.createDefendantEvent({
caseId: theCase.id,
defendantId: defendant.id,
eventType: DefendantEventType.OPENED_BY_PRISON_ADMIN,
}),
)
}
return next.handle()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@ import type { User as TUser } from '@island.is/judicial-system/types'
import {
CaseState,
CaseType,
DefendantEventType,
indictmentCases,
investigationCases,
restrictionCases,
UserRole,
} from '@island.is/judicial-system/types'

import { nowFactory } from '../../factories'
import { defenderRule, prisonSystemStaffRule } from '../../guards'
import { DefendantService } from '../defendant'
import { EventService } from '../event'
import { User } from '../user'
import { TransitionCaseDto } from './dto/transitionCase.dto'
Expand All @@ -57,6 +60,7 @@ import {
} from './guards/rolesRules'
import { CaseInterceptor } from './interceptors/case.interceptor'
import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor'
import { DefendantIndictmentAccessedInterceptor } from './interceptors/defendantIndictmentAccessed.interceptor'
import { LimitedAccessCaseFileInterceptor } from './interceptors/limitedAccessCaseFile.interceptor'
import { Case } from './models/case.model'
import { transitionCase } from './state/case.state'
Expand All @@ -73,6 +77,7 @@ export class LimitedAccessCaseController {
private readonly limitedAccessCaseService: LimitedAccessCaseService,
private readonly eventService: EventService,
private readonly pdfService: PdfService,
private readonly defendantService: DefendantService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}

Expand All @@ -84,6 +89,7 @@ export class LimitedAccessCaseController {
)
@RolesRules(prisonSystemStaffRule, defenderRule)
@UseInterceptors(
DefendantIndictmentAccessedInterceptor,
CompletedAppealAccessedInterceptor,
LimitedAccessCaseFileInterceptor,
CaseInterceptor,
Expand All @@ -100,7 +106,7 @@ export class LimitedAccessCaseController {
): Promise<Case> {
this.logger.debug(`Getting limitedAccess case ${caseId} by id`)

if (!theCase.openedByDefender) {
if (user.role === UserRole.DEFENDER && !theCase.openedByDefender) {
unakb marked this conversation as resolved.
Show resolved Hide resolved
const updated = await this.limitedAccessCaseService.update(
theCase,
{ openedByDefender: nowFactory() },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { uuid } from 'uuidv4'

import type { User } from '@island.is/judicial-system/types'
import { type User, UserRole } from '@island.is/judicial-system/types'

import { createTestingCaseModule } from '../createTestingCaseModule'

Expand All @@ -14,14 +14,18 @@ interface Then {
error: Error
}

type GivenWhenThen = (caseId: string, theCase: Case) => Promise<Then>
type GivenWhenThen = (
caseId: string,
theCase: Case,
user?: User,
) => Promise<Then>

describe('LimitedAccessCaseController - Get by id', () => {
let givenWhenThen: GivenWhenThen
const openedBeforeDate = randomDate()
const openedNowDate = randomDate()
const caseId = uuid()
const user = { id: uuid() } as User
const defaultUser = { id: uuid() } as User

let mockCaseModel: typeof Case

Expand All @@ -42,7 +46,11 @@ describe('LimitedAccessCaseController - Get by id', () => {
const mockFindOne = mockCaseModel.findOne as jest.Mock
mockFindOne.mockResolvedValue(updatedCase)

givenWhenThen = async (caseId: string, theCase: Case) => {
givenWhenThen = async (
caseId: string,
theCase: Case,
user = defaultUser,
) => {
const then = {} as Then

try {
Expand Down Expand Up @@ -79,11 +87,11 @@ describe('LimitedAccessCaseController - Get by id', () => {

describe('case exists and has not been opened by defender before', () => {
const theCase = { id: caseId } as Case

const user = { ...defaultUser, role: UserRole.DEFENDER } as User
let then: Then

beforeEach(async () => {
then = await givenWhenThen(caseId, theCase)
then = await givenWhenThen(caseId, theCase, user)
})

it('should update openedByDefender and return case', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,22 @@ export class DefendantService {
return updatedDefendant
}

async createDefendantEvent({
caseId,
defendantId,
eventType,
}: {
caseId: string
defendantId: string
eventType: DefendantEventType
}): Promise<void> {
await this.defendantEventLogModel.create({
caseId,
defendantId,
eventType,
})
}

async updateIndictmentCaseDefendant(
theCase: Case,
defendant: Defendant,
Expand All @@ -284,7 +300,7 @@ export class DefendantService {
)

if (update.isSentToPrisonAdmin) {
this.defendantEventLogModel.create({
this.createDefendantEvent({
caseId: theCase.id,
defendantId: defendant.id,
eventType: DefendantEventType.SENT_TO_PRISON_ADMIN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ import { Defendant } from './defendant.model'
timestamps: true,
})
export class DefendantEventLog extends Model {
static sentToPrisonAdminDate(defendantEventLogs?: DefendantEventLog[]) {
// gets the latest log date of a given type, since the defendant event logs are sorted
static getDefendantEventLogTypeDate({
defendantEventLogs,
eventType,
}: {
defendantEventLogs?: DefendantEventLog[]
eventType: DefendantEventType
}) {
return defendantEventLogs?.find(
(defendantEventLog) =>
defendantEventLog.eventType === DefendantEventType.SENT_TO_PRISON_ADMIN,
)
(defendantEventLog) => defendantEventLog.eventType === eventType,
)?.created
}

@Column({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ query Case($input: CaseQueryInput!) {
subpoenaType
isSentToPrisonAdmin
sentToPrisonAdminDate
openedByPrisonAdminDate
punishmentType
subpoenas {
id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ query LimitedAccessCase($input: CaseQueryInput!) {
subpoenaType
isSentToPrisonAdmin
sentToPrisonAdminDate
openedByPrisonAdminDate
punishmentType
subpoenas {
id
Expand Down
16 changes: 16 additions & 0 deletions apps/judicial-system/web/src/components/Tags/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,19 @@ export const getIndictmentRulingDecisionTag = (
return { color: 'darkerBlue', text: strings.complete }
}
}

export const getPrisonCaseStateTag = (
prisonCaseState: CaseState,
): {
color: TagVariant
text: { id: string; defaultMessage: string; description: string }
} => {
switch (prisonCaseState) {
case CaseState.NEW:
return { color: 'purple', text: strings.new }
case CaseState.RECEIVED:
return { color: 'blue', text: strings.received }
default:
return { color: 'darkerBlue', text: strings.complete }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ export const strings = defineMessages({
indictmentCompletedTitle: {
id: 'judicial.system.core:indictment_overview.indictment_completed_title',
defaultMessage: 'Dómsuppkvaðning {date}',
description: 'Titill á yfirliti ákæru fyrir fangelsi',
description: 'Undirtitill á yfirliti ákæru fyrir fangelsi',
},
indictmentReceivedTitle: {
id: 'judicial.system.core:indictment_overview.indictment_received_title',
defaultMessage: 'Móttekið {date}',
description: 'Undirtitill á yfirliti ákæru fyrir fangelsi',
},
infoCardDefendantsTitle: {
id: 'judicial.system.core:indictment_overview.info_card_defendants_title',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ const IndictmentOverview = () => {
})}
</Text>
)}
{defendant?.openedByPrisonAdminDate && (
<Text variant="h4" as="h3">
{formatMessage(strings.indictmentReceivedTitle, {
date: formatDate(defendant.openedByPrisonAdminDate, 'PPP'),
})}
</Text>
)}
</Box>
<Box marginBottom={5}>
<InfoCardClosedIndictment displayVerdictViewDate />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
titles,
} from '@island.is/judicial-system-web/messages'
import {
CaseTag,
Logo,
PageHeader,
SectionHeading,
Expand All @@ -31,6 +32,7 @@ import {
getDurationDate,
} from '@island.is/judicial-system-web/src/components/Table'
import Table from '@island.is/judicial-system-web/src/components/Table/Table'
import { getPrisonCaseStateTag } from '@island.is/judicial-system-web/src/components/Tags/utils'
import {
CaseListEntry,
CaseState,
Expand Down Expand Up @@ -217,11 +219,23 @@ export const PrisonCases: FC = () => {
),
},
{
cell: () => (
<Tag variant="purple" outlined disabled truncate>
{'Nýtt'}
</Tag>
),
cell: (row) => {
const prisonCaseState =
row.defendants &&
row.defendants?.length > 0 &&
row.defendants[0].openedByPrisonAdminDate
? CaseState.RECEIVED
: CaseState.NEW
const prisonCaseStateTag =
getPrisonCaseStateTag(prisonCaseState)

return (
<CaseTag
color={prisonCaseStateTag.color}
text={formatMessage(prisonCaseStateTag.text)}
/>
)
},
},
]}
generateContextMenuItems={(row) => [openCaseInNewTabMenuItem(row.id)]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ query Cases {
defenderChoice
verdictViewDate
isSentToPrisonAdmin
openedByPrisonAdminDate
}
courtDate
isValidToDateInThePast
Expand Down
Loading
Loading