Skip to content

Commit

Permalink
feat(j-s): Add received date and new table state tag for indictment p…
Browse files Browse the repository at this point in the history
…rison cases (#17279)

* feat(j-s): adding interceptor with when prison admin fetches indictment

* feat(j-s): Show openend by prison admin date + formatting

* fix(j-s): show received tag in prison cases when openendByPrisonAdminDate is populated

* refactor(j-s): Use case tags

* refactor(j-s): small fixes

* chore: nx format:write update dirty files

* refactor(j-s): address self-review

* fix(j-s): add user role in get by id test

* chore: nx format:write update dirty files

---------

Co-authored-by: andes-it <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Dec 19, 2024
1 parent 07f6c77 commit 72a8ce9
Show file tree
Hide file tree
Showing 17 changed files with 187 additions and 19 deletions.
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
}
10 changes: 10 additions & 0 deletions apps/judicial-system/backend/src/app/modules/case/case.service.ts
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 = (
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 {
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) {
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
24 changes: 19 additions & 5 deletions apps/judicial-system/web/src/routes/Shared/Cases/PrisonCases.tsx
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

0 comments on commit 72a8ce9

Please sign in to comment.