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): Judge confirms defender and civil claimant defense choices #16480

Merged
merged 22 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a5ac1dc
feat(j-s): Allow judge to confirm defender and civil claimant choices
unakb Oct 18, 2024
1063161
Merge branch 'main' into j-s/judge-confirms-defense-choices
unakb Oct 18, 2024
8860967
Update SelectDefender.tsx
unakb Oct 18, 2024
c087faf
cleanup
unakb Oct 18, 2024
dddef74
fix(j-s): Cleanup
unakb Oct 18, 2024
451e98d
chore: nx format:write update dirty files
andes-it Oct 18, 2024
f490663
feat(j-s): Add case files shared with defender front end handling
unakb Oct 21, 2024
061cc7d
Merge branch 'main' into j-s/judge-confirms-defense-choices
unakb Oct 21, 2024
2905443
fix(j-s): Control access with new flags
unakb Oct 21, 2024
33ec610
fix(j-s): Feedback
unakb Oct 21, 2024
71b638c
Update SelectDefender.tsx
unakb Oct 21, 2024
feba632
fix(j-s): Tests
unakb Oct 21, 2024
acdf637
Update createDefendant.dto.ts
unakb Oct 21, 2024
3a91e85
Update limitedAccessViewCaseFileGuard.spec.ts
unakb Oct 21, 2024
aaac754
Merge branch 'main' into j-s/judge-confirms-defense-choices
unakb Oct 22, 2024
9e7d46f
fix(j-s): Internal update defendant dto separate from other update
unakb Oct 22, 2024
532e130
fix(j-s): Feedback
unakb Oct 22, 2024
16bb3e2
Update subpoena.service.ts
unakb Oct 23, 2024
74bddce
fix(j-s): Case list filter for defenders
unakb Oct 23, 2024
62f374c
Update cases.filter.spec.ts
unakb Oct 23, 2024
fc41139
Merge branch 'main' into j-s/judge-confirms-defense-choices
kodiakhq[bot] Oct 23, 2024
73f03b3
Merge branch 'main' into j-s/judge-confirms-defense-choices
kodiakhq[bot] Oct 23, 2024
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 @@ -61,4 +61,9 @@ export class UpdateCivilClaimantInput {
@IsOptional()
@Field(() => Boolean, { nullable: true })
readonly caseFilesSharedWithSpokesperson?: boolean

@Allow()
@IsOptional()
@Field(() => Boolean, { nullable: true })
readonly isSpokespersonConfirmed?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,14 @@ export class UpdateDefendantInput {
@IsOptional()
@Field(() => SubpoenaType, { nullable: true })
readonly subpoenaType?: SubpoenaType

@Allow()
@IsOptional()
@Field(() => Boolean, { nullable: true })
readonly isDefenderChoiceConfirmed?: boolean

@Allow()
@IsOptional()
@Field(() => Boolean, { nullable: true })
readonly caseFilesSharedWithDefender?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,7 @@ export class CivilClaimant {

@Field(() => Boolean, { nullable: true })
readonly caseFilesSharedWithSpokesperson?: boolean

@Field(() => Boolean, { nullable: true })
readonly isSpokespersonConfirmed?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,10 @@ export class Defendant {

@Field(() => [Subpoena], { nullable: true })
readonly subpoenas?: Subpoena[]

@Field(() => Boolean, { nullable: true })
readonly isDefenderChoiceConfirmed?: boolean

@Field(() => Boolean, { nullable: true })
readonly caseFilesSharedWithDefender?: boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) =>
Promise.all([
queryInterface.addColumn(
'defendant',
'is_defender_choice_confirmed',
{
type: Sequelize.BOOLEAN,
allowNull: true,
},
{ transaction: t },
),
queryInterface.addColumn(
'defendant',
'case_files_shared_with_defender',
{
type: Sequelize.BOOLEAN,
allowNull: true,
},
{ transaction: t },
),
]),
)
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction((t) =>
Promise.all([
queryInterface.removeColumn(
'defendant',
'is_defender_choice_confirmed',
{
transaction: t,
},
),
queryInterface.removeColumn(
'defendant',
'case_files_shared_with_defender',
{
transaction: t,
},
),
]),
)
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) =>
queryInterface.addColumn(
'civil_claimant',
'is_spokesperson_confirmed',
{
type: Sequelize.BOOLEAN,
allowNull: true,
},
{ transaction: t },
),
)
},
down: (queryInterface) => {
return queryInterface.sequelize.transaction((t) =>
queryInterface.removeColumn(
'civil_claimant',
'is_spokesperson_confirmed',
{
transaction: t,
},
),
)
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -386,13 +386,18 @@ const canDefenceUserAccessIndictmentCase = (
}

// Check case defender assignment
if (Defendant.isDefenderOfDefendant(user.nationalId, theCase.defendants)) {
if (
Defendant.isConfirmedDefenderOfDefendant(
user.nationalId,
theCase.defendants,
)
) {
return true
}

// Check case spokesperson assignment
if (
CivilClaimant.isSpokespersonOfCivilClaimant(
CivilClaimant.isConfirmedSpokespersonOfCivilClaimant(
user.nationalId,
theCase.civilClaimants,
) &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => {
[Op.in]: Sequelize.literal(`
(SELECT case_id
FROM defendant
WHERE defender_national_id in ('${normalizedNationalId}', '${formattedNationalId}'))
WHERE defender_national_id in ('${normalizedNationalId}', '${formattedNationalId}') and is_defender_choice_confirmed = true)
unakb marked this conversation as resolved.
Show resolved Hide resolved
`),
},
},
Expand All @@ -292,7 +292,7 @@ const getDefenceUserCasesQueryFilter = (user: User): WhereOptions => {
[Op.in]: Sequelize.literal(`
(SELECT case_id
FROM civil_claimant
WHERE has_spokesperson = true AND spokesperson_national_id in ('${normalizedNationalId}', '${formattedNationalId}'))
WHERE has_spokesperson = true AND spokesperson_national_id in ('${normalizedNationalId}', '${formattedNationalId}') and is_spokesperson_confirmed = true)
`),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ describe('getCasesQueryFilter', () => {
[Op.in]: Sequelize.literal(`
(SELECT case_id
FROM defendant
WHERE defender_national_id in ('${user.nationalId}', '${user.nationalId}'))
WHERE defender_national_id in ('${user.nationalId}', '${user.nationalId}') and is_defender_choice_confirmed = true)
unakb marked this conversation as resolved.
Show resolved Hide resolved
`),
},
},
Expand All @@ -468,7 +468,7 @@ describe('getCasesQueryFilter', () => {
[Op.in]: Sequelize.literal(`
(SELECT case_id
FROM civil_claimant
WHERE has_spokesperson = true AND spokesperson_national_id in ('${user.nationalId}', '${user.nationalId}'))
WHERE has_spokesperson = true AND spokesperson_national_id in ('${user.nationalId}', '${user.nationalId}') and is_spokesperson_confirmed = true)
`),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,31 @@ describe.each(defenceRoles)('defence user %s', (role) => {
const theCase = {
type,
state,
defendants: [{}, { defenderNationalId: user.nationalId }, {}],
defendants: [
{},
{
defenderNationalId: user.nationalId,
},
{},
],
dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, date: new Date() }],
} as Case

verifyNoAccess(theCase, user)
})

describe('confirmed defender assigned to case', () => {
const theCase = {
type,
state,
defendants: [
{},
{
defenderNationalId: user.nationalId,
isDefenderChoiceConfirmed: true,
},
{},
],
dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, date: new Date() }],
} as Case

Expand Down Expand Up @@ -263,13 +287,35 @@ describe.each(defenceRoles)('defence user %s', (role) => {
verifyNoAccess(theCase, user)
})

describe('spokesperson assigned to case', () => {
describe('non confirmed spokesperson assigned to case', () => {
const theCase = {
type,
state,
civilClaimants: [
{},
{
hasSpokesperson: true,
spokespersonNationalId: user.nationalId,
},
{},
],
dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, date: new Date() }],
} as Case

verifyNoAccess(theCase, user)
})

describe('confirmed spokesperson assigned to case', () => {
const theCase = {
type,
state,
civilClaimants: [
{},
{ hasSpokesperson: true, spokespersonNationalId: user.nationalId },
{
hasSpokesperson: true,
spokespersonNationalId: user.nationalId,
isSpokespersonConfirmed: true,
},
{},
],
dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, date: new Date() }],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,12 +289,17 @@ export const defenderGeneratedPdfRule: RolesRule = {
}

// Allow if the user is a defender of a defendant of the case
if (Defendant.isDefenderOfDefendant(user.nationalId, theCase.defendants)) {
if (
Defendant.isConfirmedDefenderOfDefendantWithCaseFileAccess(
user.nationalId,
theCase.defendants,
)
) {
return true
}

if (
CivilClaimant.isSpokespersonOfCivilClaimantWithCaseFileAccess(
CivilClaimant.isConfirmedSpokespersonOfCivilClaimantWithCaseFileAccess(
user.nationalId,
theCase.civilClaimants,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import type { User } from '@island.is/judicial-system/types'
import {
CaseState,
CaseType,
isDistrictCourtUser,
NotificationType,
} from '@island.is/judicial-system/types'

import { Case } from '../case/models/case.model'
import { CourtService } from '../court'
import { CreateDefendantDto } from './dto/createDefendant.dto'
import { InternalUpdateDefendantDto } from './dto/internalUpdateDefendant.dto'
import { UpdateDefendantDto } from './dto/updateDefendant.dto'
import { Defendant } from './models/defendant.model'
import { DeliverResponse } from './models/deliver.response'
Expand Down Expand Up @@ -199,8 +201,18 @@ export class DefendantService {
async updateByNationalId(
caseId: string,
defendantNationalId: string,
update: UpdateDefendantDto,
update: InternalUpdateDefendantDto,
): Promise<Defendant> {
// The reason we have a separate dto for this is because requests that end here
// are initiated by outside API's which should not be able to edit other fields
// Defendant updated originating from the judicial system should use the UpdateDefendantDto
// and go through the update method above using the defendantId.
// This is also why we set the isDefenderChoiceConfirmed to false here - the judge needs to confirm all changes.
unakb marked this conversation as resolved.
Show resolved Hide resolved
update = {
...update,
isDefenderChoiceConfirmed: false,
} as UpdateDefendantDto

unakb marked this conversation as resolved.
Show resolved Hide resolved
const [numberOfAffectedRows, defendants] = await this.defendantModel.update(
update,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { IsEnum, IsOptional, IsString } from 'class-validator'

import { ApiPropertyOptional } from '@nestjs/swagger'

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

export class InternalUpdateDefendantDto {
unakb marked this conversation as resolved.
Show resolved Hide resolved
@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly defenderName?: string

@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly defenderNationalId?: string
unakb marked this conversation as resolved.
Show resolved Hide resolved

@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly defenderEmail?: string
unakb marked this conversation as resolved.
Show resolved Hide resolved

@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly defenderPhoneNumber?: string
unakb marked this conversation as resolved.
Show resolved Hide resolved

@IsOptional()
@IsEnum(DefenderChoice)
@ApiPropertyOptional({ enum: DefenderChoice })
readonly defenderChoice?: DefenderChoice

@IsOptional()
@IsEnum(DefenderChoice)
@ApiPropertyOptional({ enum: DefenderChoice })
readonly requestedDefenderChoice?: DefenderChoice

@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly requestedDefenderNationalId?: string

@IsOptional()
@IsString()
@ApiPropertyOptional({ type: String })
readonly requestedDefenderName?: string
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ export class UpdateCivilClaimantDto {
@IsBoolean()
@ApiPropertyOptional({ type: Boolean })
readonly caseFilesSharedWithSpokesperson?: boolean

@IsOptional()
@IsBoolean()
@ApiPropertyOptional({ type: Boolean })
readonly isSpokespersonConfirmed?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,14 @@ export class UpdateDefendantDto {
@IsString()
@ApiPropertyOptional({ type: String })
readonly requestedDefenderName?: string

@IsOptional()
@IsBoolean()
@ApiPropertyOptional({ type: Boolean })
readonly isDefenderChoiceConfirmed?: boolean

@IsOptional()
@IsBoolean()
@ApiPropertyOptional({ type: Boolean })
readonly caseFilesSharedWithDefender?: boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {

import { Case, CaseExistsGuard, CurrentCase } from '../case'
import { DeliverDefendantToCourtDto } from './dto/deliverDefendantToCourt.dto'
import { UpdateDefendantDto } from './dto/updateDefendant.dto'
import { InternalUpdateDefendantDto } from './dto/internalUpdateDefendant.dto'
import { CurrentDefendant } from './guards/defendant.decorator'
import { DefendantExistsGuard } from './guards/defendantExists.guard'
import { Defendant } from './models/defendant.model'
Expand Down Expand Up @@ -71,7 +71,7 @@ export class InternalDefendantController {
@Param('caseId') caseId: string,
@Param('defendantNationalId') defendantNationalId: string,
@CurrentCase() theCase: Case,
@Body() updatedDefendantChoice: UpdateDefendantDto,
@Body() updatedDefendantChoice: InternalUpdateDefendantDto,
): Promise<Defendant> {
this.logger.debug(`Updating defendant info for ${caseId}`)

Expand Down
Loading
Loading