Skip to content

Commit

Permalink
Merge branch 'main' into j-s/withdraw-indictment
Browse files Browse the repository at this point in the history
  • Loading branch information
unakb committed Dec 19, 2024
2 parents 161892f + f31b613 commit f09bc69
Show file tree
Hide file tree
Showing 353 changed files with 9,070 additions and 4,619 deletions.
1 change: 0 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,6 @@ codemagic.yaml
/apps/services/regulations-admin-backend/ @island-is/hugsmidjan
/apps/services/user-profile/ @island-is/hugsmidjan @island-is/juni @island-is/aranja
/apps/web/components/Grant/ @island-is/hugsmidjan
/apps/web/components/PlazaCard/ @island-is/hugsmidjan
/apps/web/screens/Grants/ @island-is/hugsmidjan
/apps/web/screens/Regulations/ @island-is/hugsmidjan
/apps/web/components/Regulations/ @island-is/hugsmidjan
Expand Down
16 changes: 16 additions & 0 deletions apps/contentful-apps/pages/fields/admin-only-boolean-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FieldExtensionSDK } from '@contentful/app-sdk'
import { Paragraph } from '@contentful/f36-components'
import { BooleanEditor } from '@contentful/field-editor-boolean'
import { useSDK } from '@contentful/react-apps-toolkit'

const AdminOnlyBooleanField = () => {
const sdk = useSDK<FieldExtensionSDK>()

if (!sdk.user.spaceMembership.admin) {
return <Paragraph>(Only admins can edit this field)</Paragraph>
}

return <BooleanEditor field={sdk.field} isInitiallyDisabled={false} />
}

export default AdminOnlyBooleanField
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
}
46 changes: 35 additions & 11 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 Expand Up @@ -1317,18 +1327,29 @@ export class CaseService {
(defendant) => defendant.id === updatedDefendant.id,
)?.subpoenas?.[0]?.id !== updatedDefendant.subpoenas?.[0]?.id,
)
.map((updatedDefendant) => ({
type: MessageType.DELIVERY_TO_POLICE_SUBPOENA,
user,
caseId: theCase.id,
elementId: [
updatedDefendant.id,
updatedDefendant.subpoenas?.[0].id ?? '',
],
}))
.map((updatedDefendant) => [
{
type: MessageType.DELIVERY_TO_POLICE_SUBPOENA,
user,
caseId: theCase.id,
elementId: [
updatedDefendant.id,
updatedDefendant.subpoenas?.[0].id ?? '',
],
},
{
type: MessageType.DELIVERY_TO_COURT_SUBPOENA,
user,
caseId: theCase.id,
elementId: [
updatedDefendant.id,
updatedDefendant.subpoenas?.[0].id ?? '',
],
},
])

if (messages && messages.length > 0) {
return this.messageService.sendMessagesToQueue(messages)
return this.messageService.sendMessagesToQueue(messages.flat())
}
}

Expand Down Expand Up @@ -1431,7 +1452,10 @@ export class CaseService {
await this.addMessagesForCourtCaseConnectionToQueue(updatedCase, user)
}
} else {
if (updatedCase.prosecutorId !== theCase.prosecutorId) {
if (
!isIndictment &&
updatedCase.prosecutorId !== theCase.prosecutorId
) {
// New prosecutor
await this.addMessagesForProsecutorChangeToQueue(updatedCase, user)
}
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 @@ -116,7 +116,10 @@ export class InternalCaseController {
)
}

@UseGuards(CaseExistsGuard)
@UseGuards(
CaseExistsGuard,
new CaseTypeGuard([...restrictionCases, ...investigationCases]),
)
@Post(
`case/:caseId/${messageEndpoint[MessageType.DELIVERY_TO_COURT_PROSECUTOR]}`,
)
Expand Down
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
Expand Up @@ -897,12 +897,24 @@ describe('CaseController - Update', () => {
caseId: theCase.id,
elementId: [defendantId1, subpoenaId1],
},
{
type: MessageType.DELIVERY_TO_COURT_SUBPOENA,
user,
caseId: theCase.id,
elementId: [defendantId1, subpoenaId1],
},
{
type: MessageType.DELIVERY_TO_POLICE_SUBPOENA,
user,
caseId: theCase.id,
elementId: [defendantId2, subpoenaId2],
},
{
type: MessageType.DELIVERY_TO_COURT_SUBPOENA,
user,
caseId: theCase.id,
elementId: [defendantId2, subpoenaId2],
},
])
})
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { CanActivate } from '@nestjs/common'
import {
investigationCases,
restrictionCases,
} from '@island.is/judicial-system/types'

import { CaseExistsGuard } from '../../guards/caseExists.guard'
import { CaseTypeGuard } from '../../guards/caseType.guard'
import { InternalCaseController } from '../../internalCase.controller'

describe('InternalCaseController - Deliver prosecutor to court guards', () => {
Expand All @@ -14,19 +18,12 @@ describe('InternalCaseController - Deliver prosecutor to court guards', () => {
)
})

it('should have one guards', () => {
expect(guards).toHaveLength(1)
})

describe('CaseExistsGuard', () => {
let guard: CanActivate

beforeEach(() => {
guard = new guards[0]()
})

it('should have CaseExistsGuard as guard 1', () => {
expect(guard).toBeInstanceOf(CaseExistsGuard)
it('should have the right guard configuration', () => {
expect(guards).toHaveLength(2)
expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard)
expect(guards[1]).toBeInstanceOf(CaseTypeGuard)
expect(guards[1]).toEqual({
allowedCaseTypes: [...restrictionCases, ...investigationCases],
})
})
})
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 @@ -34,6 +34,7 @@ export enum CourtDocumentFolder {
CASE_DOCUMENTS = 'Gögn málsins',
COURT_DOCUMENTS = 'Dómar, úrskurðir og Þingbók',
APPEAL_DOCUMENTS = 'Kæra til Landsréttar',
SUBPOENA_DOCUMENTS = 'Boðanir',
}

export type Subtype = Exclude<CaseType, CaseType.INDICTMENT> | IndictmentSubtype
Expand Down Expand Up @@ -342,6 +343,7 @@ export class CourtService {

return await this.courtClientService.createCase(courtId, {
caseType: isIndictment ? 'S - Ákærumál' : 'R - Rannsóknarmál',
// TODO: send a list of subtypes when CourtService supports it
subtype: courtSubtype as string,
status: 'Skráð',
receivalDate: formatISO(receivalDate, { representation: 'date' }),
Expand Down
Loading

0 comments on commit f09bc69

Please sign in to comment.