diff --git a/apps/judicial-system/backend/src/app/formatters/formatters.ts b/apps/judicial-system/backend/src/app/formatters/formatters.ts
index d48904e27885..b5859c04b70b 100644
--- a/apps/judicial-system/backend/src/app/formatters/formatters.ts
+++ b/apps/judicial-system/backend/src/app/formatters/formatters.ts
@@ -668,49 +668,6 @@ export const formatCustodyRestrictions = (
})
}
-export const formatAdvocateAssignedEmailNotification = (
- formatMessage: FormatMessage,
- theCase: Case,
- advocateType: AdvocateType,
- overviewUrl?: string,
-): SubjectAndBody => {
- const subject =
- advocateType === AdvocateType.DEFENDER
- ? formatMessage(
- notifications.advocateAssignedEmail.subjectAccessToCaseFiles,
- {
- court: capitalize(theCase.court?.name ?? ''),
- },
- )
- : formatMessage(notifications.advocateAssignedEmail.subjectAccess, {
- courtCaseNumber: theCase.courtCaseNumber,
- })
-
- const body =
- advocateType === AdvocateType.DEFENDER
- ? formatMessage(
- notifications.advocateAssignedEmail.bodyAccessToCaseFiles,
- {
- defenderHasAccessToRVG: Boolean(overviewUrl),
- courtCaseNumber: capitalize(theCase.courtCaseNumber ?? ''),
- court: theCase.court?.name ?? '',
- courtName: theCase.court?.name.replace('dómur', 'dómi') ?? '',
- linkStart: ``,
- linkEnd: '',
- },
- )
- : formatMessage(notifications.advocateAssignedEmail.bodyAccess, {
- defenderHasAccessToRVG: Boolean(overviewUrl),
- court: theCase.court?.name,
- advocateType,
- courtCaseNumber: capitalize(theCase.courtCaseNumber ?? ''),
- linkStart: ``,
- linkEnd: '',
- })
-
- return { body, subject }
-}
-
export const formatCourtOfAppealJudgeAssignedEmailNotification = (
formatMessage: FormatMessage,
caseNumber: string,
diff --git a/apps/judicial-system/backend/src/app/formatters/index.ts b/apps/judicial-system/backend/src/app/formatters/index.ts
index 48f8af67b53e..063734cdaacb 100644
--- a/apps/judicial-system/backend/src/app/formatters/index.ts
+++ b/apps/judicial-system/backend/src/app/formatters/index.ts
@@ -21,7 +21,6 @@ export {
formatProsecutorReceivedByCourtSmsNotification,
formatDefenderCourtDateLinkEmailNotification,
formatDefenderResubmittedToCourtEmailNotification,
- formatAdvocateAssignedEmailNotification,
formatCourtIndictmentReadyForCourtEmailNotification,
formatDefenderRoute,
formatDefenderReadyForCourtEmailNotification,
diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts
index fe12aea5011d..258eeedc852f 100644
--- a/apps/judicial-system/backend/src/app/messages/notifications.ts
+++ b/apps/judicial-system/backend/src/app/messages/notifications.ts
@@ -607,32 +607,6 @@ export const notifications = {
'Notaður sem texti í tölvupósti til verjanda vegna breytingar á lengd gæslu/einangrunar/vistunar þar sem úrskurðað var í einangrun.',
},
}),
- advocateAssignedEmail: defineMessages({
- subjectAccessToCaseFiles: {
- id: 'judicial.system.backend:notifications.defender_assigned_email.subject_access_to_case_files',
- defaultMessage: '{court} - aðgangur að málsgögnum',
- description:
- 'Fyrirsögn í pósti til verjanda þegar hann er skráður á mál.',
- },
- subjectAccess: {
- id: 'judicial.system.backend:notifications.defender_assigned_email.subject_access',
- defaultMessage: 'Skráning í máli {courtCaseNumber}',
- description:
- 'Fyrirsögn í pósti til verjanda þegar hann er skráður á mál.',
- },
- bodyAccessToCaseFiles: {
- id: 'judicial.system.backend:notifications.defender_assigned_email.body_access_to_case_files',
- defaultMessage:
- '{court} hefur skráð þig verjanda í máli {courtCaseNumber}.
{defenderHasAccessToRVG, select, true {Gögn málsins eru aðgengileg á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast gögn málsins hjá {courtName} ef þau hafa ekki þegar verið afhent}}.',
- description: 'Texti í pósti til verjanda þegar hann er skráður á mál.',
- },
- bodyAccess: {
- id: 'judicial.system.backend:notifications.defender_assigned_email.body_access',
- defaultMessage:
- '{court} hefur skráð þig {advocateType, select, LAWYER {lögmann einkaréttarkröfuhafa} LEGAL_RIGHTS_PROTECTOR {réttargæslumann einkaréttarkröfuhafa} other {verjanda}} í máli {courtCaseNumber}.
{defenderHasAccessToRVG, select, true {Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast málið hjá {courtName}.}}.',
- description: 'Texti í pósti til verjanda þegar hann er skráður á mál.',
- },
- }),
defendantsNotUpdatedAtCourt: defineMessages({
subject: {
id: 'judicial.system.backend:notifications.defendants_not_updated_at_court.subject',
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts
index 950e62bf84da..285092718ba5 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/civilClaimant.service.ts
@@ -7,7 +7,11 @@ import type { Logger } from '@island.is/logging'
import { LOGGER_PROVIDER } from '@island.is/logging'
import { normalizeAndFormatNationalId } from '@island.is/judicial-system/formatters'
-import { CaseState } from '@island.is/judicial-system/types'
+import { MessageService, MessageType } from '@island.is/judicial-system/message'
+import {
+ CaseState,
+ CivilClaimantNotificationType,
+} from '@island.is/judicial-system/types'
import { Case } from '../case/models/case.model'
import { UpdateCivilClaimantDto } from './dto/updateCivilClaimant.dto'
@@ -18,6 +22,7 @@ export class CivilClaimantService {
constructor(
@InjectModel(CivilClaimant)
private readonly civilClaimantModel: typeof CivilClaimant,
+ private readonly messageService: MessageService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
@@ -27,6 +32,24 @@ export class CivilClaimantService {
})
}
+ private async sendUpdateCivilClaimantMessages(
+ update: UpdateCivilClaimantDto,
+ updatedCivilClaimant: CivilClaimant,
+ ): Promise {
+ if (update.isSpokespersonConfirmed === true) {
+ return this.messageService.sendMessagesToQueue([
+ {
+ type: MessageType.CIVIL_CLAIMANT_NOTIFICATION,
+ caseId: updatedCivilClaimant.caseId,
+ body: {
+ type: CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
+ },
+ elementId: updatedCivilClaimant.id,
+ },
+ ])
+ }
+ }
+
async update(
caseId: string,
civilClaimantId: string,
@@ -49,6 +72,8 @@ export class CivilClaimantService {
throw new Error(`Could not update civil claimant ${civilClaimantId}`)
}
+ await this.sendUpdateCivilClaimantMessages(update, civilClaimants[0])
+
return civilClaimants[0]
}
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimaint.decorator.ts b/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimaint.decorator.ts
new file mode 100644
index 000000000000..9f508c5e60e5
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimaint.decorator.ts
@@ -0,0 +1,7 @@
+import { createParamDecorator } from '@nestjs/common'
+
+import { CivilClaimant } from '../models/civilClaimant.model'
+
+export const CurrentCivilClaimant = createParamDecorator(
+ (data, { args: [_1, { req }] }): CivilClaimant => req.civilClaimant,
+)
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimantExists.guard.ts b/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimantExists.guard.ts
new file mode 100644
index 000000000000..401886582d41
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/guards/civilClaimantExists.guard.ts
@@ -0,0 +1,42 @@
+import {
+ BadRequestException,
+ CanActivate,
+ ExecutionContext,
+ Injectable,
+ NotFoundException,
+} from '@nestjs/common'
+
+import { Case } from '../../case'
+
+@Injectable()
+export class CivilClaimantExistsGuard implements CanActivate {
+ canActivate(context: ExecutionContext): boolean {
+ const request = context.switchToHttp().getRequest()
+
+ const theCase: Case = request.case
+
+ if (!theCase) {
+ throw new BadRequestException('Missing case')
+ }
+
+ const civilClaimantId = request.params.civilClaimantId
+
+ if (!civilClaimantId) {
+ throw new BadRequestException('Missing civil claimant id')
+ }
+
+ const civilClaimant = theCase.civilClaimants?.find(
+ (civilClaimants) => civilClaimants.id === civilClaimantId,
+ )
+
+ if (!civilClaimant) {
+ throw new NotFoundException(
+ `Civil claimant ${civilClaimantId} of case ${theCase.id} does not exist`,
+ )
+ }
+
+ request.civilClaimant = civilClaimant
+
+ return true
+ }
+}
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/guards/test/civilClaimantExistsGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/guards/test/civilClaimantExistsGuard.spec.ts
new file mode 100644
index 000000000000..4e5cd39db640
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/guards/test/civilClaimantExistsGuard.spec.ts
@@ -0,0 +1,117 @@
+import { uuid } from 'uuidv4'
+
+import {
+ BadRequestException,
+ ExecutionContext,
+ NotFoundException,
+} from '@nestjs/common'
+
+import { CivilClaimantExistsGuard } from '../civilClaimantExists.guard'
+
+interface Then {
+ result: boolean
+ error: Error
+}
+
+type GivenWhenThen = () => Promise
+
+describe('Civil Claimant Exists Guard', () => {
+ const mockRequest = jest.fn()
+ let givenWhenThen: GivenWhenThen
+
+ beforeEach(async () => {
+ givenWhenThen = async (): Promise => {
+ const guard = new CivilClaimantExistsGuard()
+ const then = {} as Then
+
+ try {
+ then.result = guard.canActivate({
+ switchToHttp: () => ({ getRequest: mockRequest }),
+ } as unknown as ExecutionContext)
+ } catch (error) {
+ then.error = error as Error
+ }
+
+ return then
+ }
+ })
+
+ describe('civil claimant exists', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+ const civilClaimant = { id: civilClaimantId, caseId }
+ const theCase = { id: caseId, civilClaimants: [civilClaimant] }
+ const request = {
+ params: { caseId, civilClaimantId },
+ case: theCase,
+ civilClaimant: undefined,
+ }
+ let then: Then
+
+ beforeEach(async () => {
+ mockRequest.mockReturnValueOnce(request)
+
+ then = await givenWhenThen()
+ })
+
+ it('should activate', () => {
+ expect(then.result).toBe(true)
+ expect(request.civilClaimant).toBe(civilClaimant)
+ })
+ })
+
+ describe('civil claimant does not exist', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+ const theCase = { id: caseId, civilClaimants: [] }
+ let then: Then
+
+ beforeEach(async () => {
+ mockRequest.mockReturnValueOnce({
+ params: { caseId, civilClaimantId },
+ case: theCase,
+ })
+
+ then = await givenWhenThen()
+ })
+
+ it('should throw NotFoundException', () => {
+ expect(then.error).toBeInstanceOf(NotFoundException)
+ expect(then.error.message).toBe(
+ `Civil claimant ${civilClaimantId} of case ${caseId} does not exist`,
+ )
+ })
+ })
+
+ describe('missing case', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ mockRequest.mockReturnValueOnce({ params: {} })
+
+ then = await givenWhenThen()
+ })
+
+ it('should throw BadRequestException', () => {
+ expect(then.error).toBeInstanceOf(BadRequestException)
+ expect(then.error.message).toBe('Missing case')
+ })
+ })
+
+ describe('missing civil claimant id', () => {
+ const caseId = uuid()
+ const theCase = { id: caseId, civilClaimants: [] }
+ let then: Then
+
+ beforeEach(async () => {
+ mockRequest.mockReturnValueOnce({ params: { caseId }, case: theCase })
+
+ then = await givenWhenThen()
+ })
+
+ it('should throw BadRequestException', () => {
+ expect(then.error).toBeInstanceOf(BadRequestException)
+ expect(then.error.message).toBe('Missing civil claimant id')
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/index.ts b/apps/judicial-system/backend/src/app/modules/defendant/index.ts
index d9914e3dcda4..5fb8a9c29896 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/index.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/index.ts
@@ -1,6 +1,9 @@
export { Defendant } from './models/defendant.model'
export { DefendantService } from './defendant.service'
-export { CivilClaimant } from './models/civilClaimant.model'
export { DefendantExistsGuard } from './guards/defendantExists.guard'
export { CurrentDefendant } from './guards/defendant.decorator'
+
+export { CivilClaimant } from './models/civilClaimant.model'
export { CivilClaimantService } from './civilClaimant.service'
+export { CivilClaimantExistsGuard } from './guards/civilClaimantExists.guard'
+export { CurrentCivilClaimant } from './guards/civilClaimaint.decorator'
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/create.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/create.spec.ts
new file mode 100644
index 000000000000..4684346f3209
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/create.spec.ts
@@ -0,0 +1,80 @@
+import { uuid } from 'uuidv4'
+
+import { createTestingDefendantModule } from '../createTestingDefendantModule'
+
+import { Case } from '../../../case'
+import { CivilClaimant } from '../../models/civilClaimant.model'
+
+interface Then {
+ result: CivilClaimant
+ error: Error
+}
+
+type GivenWhenThen = (caseId?: string) => Promise
+
+describe('CivilClaimantController - Create', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+ const theCase = { id: caseId } as Case
+ const civilClaimantToCreate = {
+ caseId,
+ }
+ const createdCivilClaimant = { id: civilClaimantId, caseId }
+
+ let mockCivilClaimantModel: typeof CivilClaimant
+ let givenWhenThen: GivenWhenThen
+
+ beforeEach(async () => {
+ const { civilClaimantModel, civilClaimantController } =
+ await createTestingDefendantModule()
+
+ mockCivilClaimantModel = civilClaimantModel
+
+ const mockCreate = mockCivilClaimantModel.create as jest.Mock
+ mockCreate.mockResolvedValue(createdCivilClaimant)
+
+ givenWhenThen = async () => {
+ const then = {} as Then
+
+ await civilClaimantController
+ .create(theCase.id, theCase)
+ .then((result) => (then.result = result))
+ .catch((error) => (then.error = error))
+
+ return then
+ }
+ })
+
+ describe('civil claimant creation', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ then = await givenWhenThen(caseId)
+ })
+ it('should create a civil claimant', () => {
+ expect(mockCivilClaimantModel.create).toHaveBeenCalledWith(
+ civilClaimantToCreate,
+ )
+ })
+
+ it('should return the created civil claimant', () => {
+ expect(then.result).toEqual(createdCivilClaimant)
+ })
+ })
+
+ describe('civil claimant creation fails', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ const mockCreate = mockCivilClaimantModel.create as jest.Mock
+ mockCreate.mockRejectedValue(new Error('Test error'))
+
+ then = await givenWhenThen(caseId)
+ })
+
+ it('should throw an error', () => {
+ expect(then.error).toBeInstanceOf(Error)
+ expect(then.error.message).toEqual('Test error')
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createGuards.spec.ts
new file mode 100644
index 000000000000..738952366261
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createGuards.spec.ts
@@ -0,0 +1,25 @@
+import { CanActivate } from '@nestjs/common'
+
+import { CaseExistsGuard, CaseWriteGuard } from '../../../case'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Create guards', () => {
+ let guards: Array CanActivate>
+ const expectedGuards = [CaseExistsGuard, CaseWriteGuard]
+
+ beforeEach(() => {
+ guards = Reflect.getMetadata(
+ '__guards__',
+ CivilClaimantController.prototype.create,
+ )
+ })
+
+ it('should have the correct guards in the correct order', () => {
+ expect(guards).toHaveLength(expectedGuards.length)
+
+ expectedGuards.forEach((expectedGuard, index) => {
+ const guardInstance = new guards[index]()
+ expect(guardInstance).toBeInstanceOf(expectedGuard)
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createRolesRules.spec.ts
new file mode 100644
index 000000000000..326946023e4f
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/createRolesRules.spec.ts
@@ -0,0 +1,35 @@
+import {
+ districtCourtAssistantRule,
+ districtCourtJudgeRule,
+ districtCourtRegistrarRule,
+ prosecutorRepresentativeRule,
+ prosecutorRule,
+} from '../../../../guards'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Create rules', () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let rules: any[]
+
+ const expectedRules = [
+ prosecutorRule,
+ prosecutorRepresentativeRule,
+ districtCourtJudgeRule,
+ districtCourtRegistrarRule,
+ districtCourtAssistantRule,
+ ]
+
+ beforeEach(() => {
+ rules = Reflect.getMetadata(
+ 'roles-rules',
+ CivilClaimantController.prototype.create,
+ )
+ })
+
+ it('should give permission to roles', () => {
+ expect(rules).toHaveLength(expectedRules.length)
+ expectedRules.forEach((expectedRule) =>
+ expect(rules).toContain(expectedRule),
+ )
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/delete.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/delete.spec.ts
new file mode 100644
index 000000000000..8a2c17bc5e10
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/delete.spec.ts
@@ -0,0 +1,79 @@
+import { uuid } from 'uuidv4'
+
+import { createTestingDefendantModule } from '../createTestingDefendantModule'
+
+import { CivilClaimant } from '../../models/civilClaimant.model'
+import { DeleteCivilClaimantResponse } from '../../models/deleteCivilClaimant.response'
+
+interface Then {
+ result: DeleteCivilClaimantResponse
+ error: Error
+}
+
+type GivenWhenThen = (
+ caseId?: string,
+ civilClaimaintId?: string,
+) => Promise
+
+describe('CivilClaimantController - Delete', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+
+ let mockCivilClaimantModel: typeof CivilClaimant
+ let givenWhenThen: GivenWhenThen
+
+ beforeEach(async () => {
+ const { civilClaimantController, civilClaimantModel } =
+ await createTestingDefendantModule()
+
+ mockCivilClaimantModel = civilClaimantModel
+
+ const mockDestroy = mockCivilClaimantModel.destroy as jest.Mock
+ mockDestroy.mockRejectedValue(new Error('Test error'))
+
+ givenWhenThen = async () => {
+ const then = {} as Then
+
+ try {
+ then.result = await civilClaimantController.delete(
+ caseId,
+ civilClaimantId,
+ )
+ } catch (error) {
+ then.error = error as Error
+ }
+
+ return then
+ }
+ })
+
+ describe('civil claimant deleted', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ const mockDestroy = mockCivilClaimantModel.destroy as jest.Mock
+ mockDestroy.mockResolvedValue(1)
+
+ then = await givenWhenThen(caseId, civilClaimantId)
+ })
+ it('should delete civil claimant', () => {
+ expect(mockCivilClaimantModel.destroy).toHaveBeenCalledWith({
+ where: { caseId, id: civilClaimantId },
+ })
+ expect(then.result).toEqual({ deleted: true })
+ })
+ })
+
+ describe('civil claimant deletion fails', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ then = await givenWhenThen()
+ })
+
+ it('should throw Error', () => {
+ expect(then.error).toBeInstanceOf(Error)
+ expect(then.error.message).toBe('Test error')
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts
new file mode 100644
index 000000000000..20e8ef89e2f6
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteGuards.spec.ts
@@ -0,0 +1,25 @@
+import { CanActivate } from '@nestjs/common'
+
+import { CaseExistsGuard, CaseWriteGuard } from '../../../case'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Delete guards', () => {
+ let guards: Array CanActivate>
+ const expectedGuards = [CaseExistsGuard, CaseWriteGuard]
+
+ beforeEach(() => {
+ guards = Reflect.getMetadata(
+ '__guards__',
+ CivilClaimantController.prototype.delete,
+ )
+ })
+
+ it('should have the correct guards in the correct order', () => {
+ expect(guards).toHaveLength(expectedGuards.length)
+
+ expectedGuards.forEach((expectedGuard, index) => {
+ const guardInstance = new guards[index]()
+ expect(guardInstance).toBeInstanceOf(expectedGuard)
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteRolesRules.spec.ts
new file mode 100644
index 000000000000..46039a55bc8b
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/deleteRolesRules.spec.ts
@@ -0,0 +1,26 @@
+import {
+ prosecutorRepresentativeRule,
+ prosecutorRule,
+} from '../../../../guards'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Delete rules', () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let rules: any[]
+
+ const expectedRules = [prosecutorRule, prosecutorRepresentativeRule]
+
+ beforeEach(() => {
+ rules = Reflect.getMetadata(
+ 'roles-rules',
+ CivilClaimantController.prototype.delete,
+ )
+ })
+
+ it('should give permission to roles', () => {
+ expect(rules).toHaveLength(expectedRules.length)
+ expectedRules.forEach((expectedRule) =>
+ expect(rules).toContain(expectedRule),
+ )
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts
new file mode 100644
index 000000000000..d8661906faa4
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/update.spec.ts
@@ -0,0 +1,140 @@
+import { uuid } from 'uuidv4'
+
+import { MessageService, MessageType } from '@island.is/judicial-system/message'
+import { CivilClaimantNotificationType } from '@island.is/judicial-system/types'
+
+import { createTestingDefendantModule } from '../createTestingDefendantModule'
+
+import { UpdateCivilClaimantDto } from '../../dto/updateCivilClaimant.dto'
+import { CivilClaimant } from '../../models/civilClaimant.model'
+
+interface Then {
+ result: CivilClaimant
+ error: Error
+}
+
+type GivenWhenThen = (
+ caseId: string,
+ civilClaimantId: string,
+ updateData: UpdateCivilClaimantDto,
+) => Promise
+
+describe('CivilClaimantController - Update', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+ const civilClaimaint = {
+ id: civilClaimantId,
+ caseId,
+ nationalId: uuid(),
+ name: 'Original Name',
+ } as CivilClaimant
+
+ let mockMessageService: MessageService
+ let mockCivilClaimantModel: typeof CivilClaimant
+ let givenWhenThen: GivenWhenThen
+
+ beforeEach(async () => {
+ const { messageService, civilClaimantModel, civilClaimantController } =
+ await createTestingDefendantModule()
+
+ mockMessageService = messageService
+ mockCivilClaimantModel = civilClaimantModel
+
+ givenWhenThen = async (
+ caseId: string,
+ civilClaimantId: string,
+ updateData: UpdateCivilClaimantDto,
+ ) => {
+ const then = {} as Then
+
+ await civilClaimantController
+ .update(caseId, civilClaimantId, updateData)
+ .then((result) => (then.result = result))
+ .catch((error) => (then.error = error))
+
+ return then
+ }
+ })
+
+ describe('civil claimant updated', () => {
+ const civilClaimantUpdate = { name: 'Updated Name' }
+ const updatedCivilClaimant = {
+ id: civilClaimantId,
+ caseId,
+ ...civilClaimantUpdate,
+ }
+ let then: Then
+
+ beforeEach(async () => {
+ const mockUpdate = mockCivilClaimantModel.update as jest.Mock
+ mockUpdate.mockResolvedValueOnce([1, [updatedCivilClaimant]])
+
+ then = await givenWhenThen(caseId, civilClaimantId, civilClaimantUpdate)
+ })
+
+ it('should update the civil claimant', () => {
+ expect(mockCivilClaimantModel.update).toHaveBeenCalledWith(
+ civilClaimantUpdate,
+ {
+ where: { id: civilClaimantId, caseId },
+ returning: true,
+ },
+ )
+ expect(mockMessageService.sendMessagesToQueue).not.toHaveBeenCalled()
+ })
+
+ it('should return the updated civil claimant', () => {
+ expect(then.result).toBe(updatedCivilClaimant)
+ })
+ })
+
+ describe('civil claimant spokesperson confirmed', () => {
+ const civilClaimantUpdate = { isSpokespersonConfirmed: true }
+ const updatedCivilClaimant = {
+ id: civilClaimantId,
+ caseId,
+ ...civilClaimantUpdate,
+ }
+ let then: Then
+
+ beforeEach(async () => {
+ const mockUpdate = mockCivilClaimantModel.update as jest.Mock
+ mockUpdate.mockResolvedValueOnce([1, [updatedCivilClaimant]])
+
+ then = await givenWhenThen(caseId, civilClaimantId, civilClaimantUpdate)
+ })
+
+ it('should queue spokesperson assigned message', () => {
+ expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([
+ {
+ type: MessageType.CIVIL_CLAIMANT_NOTIFICATION,
+ caseId,
+ elementId: civilClaimantId,
+ body: {
+ type: CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
+ },
+ },
+ ])
+ })
+
+ it('should return the updated civil claimant', () => {
+ expect(then.result).toBe(updatedCivilClaimant)
+ })
+ })
+
+ describe('civil claimant update fails', () => {
+ let then: Then
+
+ beforeEach(async () => {
+ const mockUpdate = mockCivilClaimantModel.update as jest.Mock
+ mockUpdate.mockRejectedValue(new Error('Test error'))
+
+ then = await givenWhenThen(caseId, civilClaimantId, {})
+ })
+
+ it('should throw an error', () => {
+ expect(then.error).toBeInstanceOf(Error)
+ expect(then.error.message).toEqual('Test error')
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts
new file mode 100644
index 000000000000..d333af01f86f
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateGuards.spec.ts
@@ -0,0 +1,25 @@
+import { CanActivate } from '@nestjs/common'
+
+import { CaseExistsGuard, CaseWriteGuard } from '../../../case'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Update guards', () => {
+ let guards: Array CanActivate>
+ const expectedGuards = [CaseExistsGuard, CaseWriteGuard]
+
+ beforeEach(() => {
+ guards = Reflect.getMetadata(
+ '__guards__',
+ CivilClaimantController.prototype.update,
+ )
+ })
+
+ it('should have the correct guards in the correct order', () => {
+ expect(guards).toHaveLength(expectedGuards.length)
+
+ expectedGuards.forEach((expectedGuard, index) => {
+ const guardInstance = new guards[index]()
+ expect(guardInstance).toBeInstanceOf(expectedGuard)
+ })
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateRolesRules.spec.ts
new file mode 100644
index 000000000000..38a579850480
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/civilClaimantController/updateRolesRules.spec.ts
@@ -0,0 +1,35 @@
+import {
+ districtCourtAssistantRule,
+ districtCourtJudgeRule,
+ districtCourtRegistrarRule,
+ prosecutorRepresentativeRule,
+ prosecutorRule,
+} from '../../../../guards'
+import { CivilClaimantController } from '../../civilClaimant.controller'
+
+describe('CivilClaimantController - Update rules', () => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let rules: any[]
+
+ const expectedRules = [
+ prosecutorRule,
+ prosecutorRepresentativeRule,
+ districtCourtJudgeRule,
+ districtCourtRegistrarRule,
+ districtCourtAssistantRule,
+ ]
+
+ beforeEach(() => {
+ rules = Reflect.getMetadata(
+ 'roles-rules',
+ CivilClaimantController.prototype.update,
+ )
+ })
+
+ it('should give permission to roles', () => {
+ expect(rules).toHaveLength(expectedRules.length)
+ expectedRules.forEach((expectedRule) =>
+ expect(rules).toContain(expectedRule),
+ )
+ })
+})
diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/createTestingDefendantModule.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/createTestingDefendantModule.ts
index 3b18ac9b8c8c..7ca4abf0ae2d 100644
--- a/apps/judicial-system/backend/src/app/modules/defendant/test/createTestingDefendantModule.ts
+++ b/apps/judicial-system/backend/src/app/modules/defendant/test/createTestingDefendantModule.ts
@@ -13,9 +13,12 @@ import { MessageService } from '@island.is/judicial-system/message'
import { CaseService } from '../../case'
import { CourtService } from '../../court'
import { UserService } from '../../user'
+import { CivilClaimantController } from '../civilClaimant.controller'
+import { CivilClaimantService } from '../civilClaimant.service'
import { DefendantController } from '../defendant.controller'
import { DefendantService } from '../defendant.service'
import { InternalDefendantController } from '../internalDefendant.controller'
+import { CivilClaimant } from '../models/civilClaimant.model'
import { Defendant } from '../models/defendant.model'
jest.mock('@island.is/judicial-system/message')
@@ -26,7 +29,11 @@ jest.mock('../../case/case.service')
export const createTestingDefendantModule = async () => {
const defendantModule = await Test.createTestingModule({
imports: [ConfigModule.forRoot({ load: [sharedAuthModuleConfig] })],
- controllers: [DefendantController, InternalDefendantController],
+ controllers: [
+ DefendantController,
+ InternalDefendantController,
+ CivilClaimantController,
+ ],
providers: [
SharedAuthModule,
MessageService,
@@ -52,7 +59,19 @@ export const createTestingDefendantModule = async () => {
findByPk: jest.fn(),
},
},
+ {
+ provide: getModelToken(CivilClaimant),
+ useValue: {
+ findOne: jest.fn(),
+ findAll: jest.fn(),
+ create: jest.fn(),
+ update: jest.fn(),
+ destroy: jest.fn(),
+ findByPk: jest.fn(),
+ },
+ },
DefendantService,
+ CivilClaimantService,
],
}).compile()
@@ -77,6 +96,17 @@ export const createTestingDefendantModule = async () => {
InternalDefendantController,
)
+ const civilClaimantModel = await defendantModule.resolve<
+ typeof CivilClaimant
+ >(getModelToken(CivilClaimant))
+
+ const civilClaimantService =
+ defendantModule.get(CivilClaimantService)
+
+ const civilClaimantController = defendantModule.get(
+ CivilClaimantController,
+ )
+
defendantModule.close()
return {
@@ -87,5 +117,8 @@ export const createTestingDefendantModule = async () => {
defendantService,
defendantController,
internalDefendantController,
+ civilClaimantService,
+ civilClaimantController,
+ civilClaimantModel,
}
}
diff --git a/apps/judicial-system/backend/src/app/modules/notification/caseNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/caseNotification.service.ts
index beca473bbbb5..5f47cbac54c6 100644
--- a/apps/judicial-system/backend/src/app/modules/notification/caseNotification.service.ts
+++ b/apps/judicial-system/backend/src/app/modules/notification/caseNotification.service.ts
@@ -50,7 +50,6 @@ import {
} from '@island.is/judicial-system/types'
import {
- formatAdvocateAssignedEmailNotification,
formatCourtHeadsUpSmsNotification,
formatCourtIndictmentReadyForCourtEmailNotification,
formatCourtOfAppealJudgeAssignedEmailNotification,
@@ -1506,17 +1505,7 @@ export class CaseNotificationService extends BaseNotificationService {
if (!advocateEmail) {
return false
}
- if (isIndictmentCase(theCase.type)) {
- const hasSentNotificationBefore = this.hasReceivedNotification(
- CaseNotificationType.ADVOCATE_ASSIGNED,
- advocateEmail,
- theCase.notifications,
- )
-
- if (hasSentNotificationBefore) {
- return false
- }
- } else if (isInvestigationCase(theCase.type)) {
+ if (isInvestigationCase(theCase.type)) {
const isDefenderIncludedInSessionArrangements =
theCase.sessionArrangements &&
[
@@ -1527,7 +1516,7 @@ export class CaseNotificationService extends BaseNotificationService {
if (!isDefenderIncludedInSessionArrangements) {
return false
}
- } else {
+ } else if (isRequestCase(theCase.type)) {
const hasDefenderBeenNotified = this.hasReceivedNotification(
[
CaseNotificationType.READY_FOR_COURT,
@@ -1546,70 +1535,12 @@ export class CaseNotificationService extends BaseNotificationService {
return true
}
- private sendAdvocateAssignedNotification(
- theCase: Case,
- advocateType: AdvocateType,
- advocateNationalId?: string,
- advocateName?: string,
- advocateEmail?: string,
- ): Promise {
- const { subject, body } = formatAdvocateAssignedEmailNotification(
- this.formatMessage,
- theCase,
- advocateType,
- advocateNationalId &&
- formatDefenderRoute(this.config.clientUrl, theCase.type, theCase.id),
- )
-
- return this.sendEmail(
- subject,
- body,
- advocateName,
- advocateEmail,
- undefined,
- Boolean(advocateNationalId) === false,
- )
- }
-
private async sendAdvocateAssignedNotifications(
theCase: Case,
): Promise {
const promises: Promise[] = []
- if (isIndictmentCase(theCase.type)) {
- if (theCase.civilClaimants) {
- for (const civilClaimant of theCase.civilClaimants) {
- const {
- spokespersonEmail,
- spokespersonIsLawyer,
- spokespersonName,
- spokespersonNationalId,
- hasSpokesperson,
- } = civilClaimant
-
- const shouldSend =
- hasSpokesperson &&
- this.shouldSendAdvocateAssignedNotification(
- theCase,
- spokespersonEmail,
- )
-
- if (shouldSend === true) {
- promises.push(
- this.sendAdvocateAssignedNotification(
- theCase,
- spokespersonIsLawyer
- ? AdvocateType.LAWYER
- : AdvocateType.LEGAL_RIGHTS_PROTECTOR,
- spokespersonNationalId,
- spokespersonName,
- spokespersonEmail,
- ),
- )
- }
- }
- }
- } else if (DateLog.arraignmentDate(theCase.dateLogs)?.date) {
+ if (DateLog.arraignmentDate(theCase.dateLogs)?.date) {
const shouldSend = this.shouldSendAdvocateAssignedNotification(
theCase,
theCase.defenderEmail,
diff --git a/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.service.ts
new file mode 100644
index 000000000000..c80250801348
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.service.ts
@@ -0,0 +1,171 @@
+import { MessageDescriptor } from '@formatjs/intl'
+
+import {
+ Inject,
+ Injectable,
+ InternalServerErrorException,
+} from '@nestjs/common'
+import { InjectModel } from '@nestjs/sequelize'
+
+import { IntlService } from '@island.is/cms-translations'
+import { EmailService } from '@island.is/email-service'
+import { type Logger, LOGGER_PROVIDER } from '@island.is/logging'
+import { type ConfigType } from '@island.is/nest/config'
+
+import { DEFENDER_INDICTMENT_ROUTE } from '@island.is/judicial-system/consts'
+import { capitalize } from '@island.is/judicial-system/formatters'
+import { CivilClaimantNotificationType } from '@island.is/judicial-system/types'
+
+import { Case } from '../case'
+import { CivilClaimant } from '../defendant'
+import { EventService } from '../event'
+import { DeliverResponse } from './models/deliver.response'
+import { Notification, Recipient } from './models/notification.model'
+import { BaseNotificationService } from './baseNotification.service'
+import { strings } from './civilClaimantNotification.strings'
+import { notificationModuleConfig } from './notification.config'
+
+@Injectable()
+export class CivilClaimantNotificationService extends BaseNotificationService {
+ constructor(
+ @InjectModel(Notification)
+ notificationModel: typeof Notification,
+ @Inject(notificationModuleConfig.KEY)
+ config: ConfigType,
+ @Inject(LOGGER_PROVIDER) logger: Logger,
+ intlService: IntlService,
+ emailService: EmailService,
+ eventService: EventService,
+ ) {
+ super(
+ notificationModel,
+ emailService,
+ intlService,
+ config,
+ eventService,
+ logger,
+ )
+ }
+
+ private async sendEmails(
+ civilClaimant: CivilClaimant,
+ theCase: Case,
+ notificationType: CivilClaimantNotificationType,
+ subject: MessageDescriptor,
+ body: MessageDescriptor,
+ ) {
+ const courtName = capitalize(theCase.court?.name)
+ const courtCaseNumber = theCase.courtCaseNumber
+ const spokespersonHasAccessToRVG = !!civilClaimant.spokespersonNationalId
+
+ const formattedSubject = this.formatMessage(subject, {
+ courtName,
+ courtCaseNumber,
+ })
+
+ const formattedBody = this.formatMessage(body, {
+ courtName,
+ courtCaseNumber,
+ spokespersonHasAccessToRVG,
+ spokespersonIsLawyer: civilClaimant.spokespersonIsLawyer,
+ linkStart: ``,
+ linkEnd: '',
+ })
+ const promises: Promise[] = []
+
+ if (civilClaimant.isSpokespersonConfirmed) {
+ promises.push(
+ this.sendEmail(
+ formattedSubject,
+ formattedBody,
+ civilClaimant.spokespersonName,
+ civilClaimant.spokespersonEmail,
+ undefined,
+ true,
+ ),
+ )
+ }
+
+ const recipients = await Promise.all(promises)
+
+ return this.recordNotification(theCase.id, notificationType, recipients)
+ }
+
+ private shouldSendSpokespersonAssignedNotification(
+ theCase: Case,
+ civilClaimant: CivilClaimant,
+ ): boolean {
+ if (
+ !civilClaimant.spokespersonEmail ||
+ !civilClaimant.isSpokespersonConfirmed
+ ) {
+ return false
+ }
+
+ const hasSentNotificationBefore = this.hasReceivedNotification(
+ CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
+ civilClaimant.spokespersonEmail,
+ theCase.notifications,
+ )
+
+ if (!hasSentNotificationBefore) {
+ return true
+ }
+
+ return false
+ }
+
+ private async sendSpokespersonAssignedNotification(
+ civilClaimant: CivilClaimant,
+ theCase: Case,
+ ): Promise {
+ const shouldSend = this.shouldSendSpokespersonAssignedNotification(
+ theCase,
+ civilClaimant,
+ )
+
+ if (shouldSend) {
+ return this.sendEmails(
+ civilClaimant,
+ theCase,
+ CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
+ strings.civilClaimantSpokespersonAssignedSubject,
+ strings.civilClaimantSpokespersonAssignedBody,
+ )
+ }
+
+ // Nothing should be sent so we return a successful response
+ return { delivered: true }
+ }
+
+ private sendNotification(
+ notificationType: CivilClaimantNotificationType,
+ civilClaimant: CivilClaimant,
+ theCase: Case,
+ ): Promise {
+ switch (notificationType) {
+ case CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED:
+ return this.sendSpokespersonAssignedNotification(civilClaimant, theCase)
+ default:
+ throw new InternalServerErrorException(
+ `Invalid notification type: ${notificationType}`,
+ )
+ }
+ }
+
+ async sendCivilClaimantNotification(
+ type: CivilClaimantNotificationType,
+ civilClaimant: CivilClaimant,
+ theCase: Case,
+ ): Promise {
+ await this.refreshFormatMessage()
+
+ try {
+ return await this.sendNotification(type, civilClaimant, theCase)
+ } catch (error) {
+ this.logger.error('Failed to send notification', error)
+
+ return { delivered: false }
+ }
+ }
+}
diff --git a/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.strings.ts b/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.strings.ts
new file mode 100644
index 000000000000..d9beac015345
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/notification/civilClaimantNotification.strings.ts
@@ -0,0 +1,17 @@
+import { defineMessage } from '@formatjs/intl'
+
+export const strings = {
+ civilClaimantSpokespersonAssignedSubject: defineMessage({
+ id: 'judicial.system.backend:civil_claimant_notifications.spokesperson_assigned_subject',
+ defaultMessage: '{courtName} - aðgangur að máli',
+ description:
+ 'Subject of the notification when a civil claimant spokesperson is assigned and confirmed',
+ }),
+ civilClaimantSpokespersonAssignedBody: defineMessage({
+ id: 'judicial.system.backend:civil_claimant_notifications.indictment_assigned_body',
+ defaultMessage:
+ '{courtName} hefur skráð þig {spokespersonIsLawyer, select, true {lögmann einkaréttarkröfuhafa} other {réttargæslumann einkaréttarkröfuhafa}} í máli {courtCaseNumber}.
{spokespersonHasAccessToRVG, select, true {Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Þú getur nálgast málið hjá dómstólnum.}}.',
+ description:
+ 'Body of the notification when a civil claimant spokesperson is assigned and confirmed',
+ }),
+}
diff --git a/apps/judicial-system/backend/src/app/modules/notification/dto/civilClaimantNotification.dto.ts b/apps/judicial-system/backend/src/app/modules/notification/dto/civilClaimantNotification.dto.ts
new file mode 100644
index 000000000000..5557dbdfe354
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/notification/dto/civilClaimantNotification.dto.ts
@@ -0,0 +1,12 @@
+import { IsEnum, IsNotEmpty } from 'class-validator'
+
+import { ApiProperty } from '@nestjs/swagger'
+
+import { CivilClaimantNotificationType } from '@island.is/judicial-system/types'
+
+export class CivilClaimantNotificationDto {
+ @IsNotEmpty()
+ @IsEnum(CivilClaimantNotificationType)
+ @ApiProperty({ enum: CivilClaimantNotificationType })
+ readonly type!: CivilClaimantNotificationType
+}
diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts
index 53237a099dd2..c0e2ca1a0003 100644
--- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts
+++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts
@@ -18,15 +18,24 @@ import {
} from '@island.is/judicial-system/message'
import { Case, CaseHasExistedGuard, CurrentCase } from '../case'
-import { CurrentDefendant, Defendant, DefendantExistsGuard } from '../defendant'
+import {
+ CivilClaimant,
+ CivilClaimantExistsGuard,
+ CurrentCivilClaimant,
+ CurrentDefendant,
+ Defendant,
+ DefendantExistsGuard,
+} from '../defendant'
import { SubpoenaExistsGuard } from '../subpoena'
import { CaseNotificationDto } from './dto/caseNotification.dto'
+import { CivilClaimantNotificationDto } from './dto/civilClaimantNotification.dto'
import { DefendantNotificationDto } from './dto/defendantNotification.dto'
import { InstitutionNotificationDto } from './dto/institutionNotification.dto'
import { NotificationDispatchDto } from './dto/notificationDispatch.dto'
import { SubpoenaNotificationDto } from './dto/subpoenaNotification.dto'
import { DeliverResponse } from './models/deliver.response'
import { CaseNotificationService } from './caseNotification.service'
+import { CivilClaimantNotificationService } from './civilClaimantNotification.service'
import { DefendantNotificationService } from './defendantNotification.service'
import { InstitutionNotificationService } from './institutionNotification.service'
import { NotificationDispatchService } from './notificationDispatch.service'
@@ -42,6 +51,7 @@ export class InternalNotificationController {
private readonly institutionNotificationService: InstitutionNotificationService,
private readonly subpoenaNotificationService: SubpoenaNotificationService,
private readonly defendantNotificationService: DefendantNotificationService,
+ private readonly civilClaimantNotificationService: CivilClaimantNotificationService,
@Inject(LOGGER_PROVIDER) private readonly logger: Logger,
) {}
@@ -123,6 +133,34 @@ export class InternalNotificationController {
)
}
+ @Post(
+ `case/:caseId/${
+ messageEndpoint[MessageType.CIVIL_CLAIMANT_NOTIFICATION]
+ }/:civilClaimantId`,
+ )
+ @UseGuards(CaseHasExistedGuard, CivilClaimantExistsGuard)
+ @ApiCreatedResponse({
+ type: DeliverResponse,
+ description: 'Sends civil claimant related notifications',
+ })
+ sendCivilClaimantNotification(
+ @Param('caseId') caseId: string,
+ @Param('civilClaimantId') civilClaimantId: string,
+ @CurrentCase() theCase: Case,
+ @CurrentCivilClaimant() civilClaimant: CivilClaimant,
+ @Body() notificationDto: CivilClaimantNotificationDto,
+ ): Promise {
+ this.logger.debug(
+ `Sending ${notificationDto.type} notification for civil claimant ${civilClaimantId} and case ${caseId}`,
+ )
+
+ return this.civilClaimantNotificationService.sendCivilClaimantNotification(
+ notificationDto.type,
+ civilClaimant,
+ theCase,
+ )
+ }
+
@Post(messageEndpoint[MessageType.NOTIFICATION_DISPATCH])
@ApiCreatedResponse({
type: DeliverResponse,
diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts
index 6dcb837ace05..14fb32c0a565 100644
--- a/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts
+++ b/apps/judicial-system/backend/src/app/modules/notification/notification.module.ts
@@ -18,6 +18,7 @@ import {
} from '../index'
import { Notification } from './models/notification.model'
import { CaseNotificationService } from './caseNotification.service'
+import { CivilClaimantNotificationService } from './civilClaimantNotification.service'
import { DefendantNotificationService } from './defendantNotification.service'
import { InstitutionNotificationService } from './institutionNotification.service'
import { InternalNotificationController } from './internalNotification.controller'
@@ -43,12 +44,13 @@ import { SubpoenaNotificationService } from './subpoenaNotification.service'
],
controllers: [NotificationController, InternalNotificationController],
providers: [
- NotificationService,
CaseNotificationService,
- NotificationDispatchService,
+ CivilClaimantNotificationService,
+ DefendantNotificationService,
InstitutionNotificationService,
+ NotificationService,
+ NotificationDispatchService,
SubpoenaNotificationService,
- DefendantNotificationService,
],
})
export class NotificationModule {}
diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts
index a269d181001b..267327b2235b 100644
--- a/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts
+++ b/apps/judicial-system/backend/src/app/modules/notification/test/createTestingNotificationModule.ts
@@ -25,6 +25,7 @@ import { eventModuleConfig, EventService } from '../../event'
import { InstitutionService } from '../../institution'
import { UserService } from '../../user'
import { CaseNotificationService } from '../caseNotification.service'
+import { CivilClaimantNotificationService } from '../civilClaimantNotification.service'
import { DefendantNotificationService } from '../defendantNotification.service'
import { InstitutionNotificationService } from '../institutionNotification.service'
import { InternalNotificationController } from '../internalNotification.controller'
@@ -104,6 +105,7 @@ export const createTestingNotificationModule = async () => {
NotificationDispatchService,
InstitutionNotificationService,
DefendantNotificationService,
+ CivilClaimantNotificationService,
],
})
.useMocker((token) => {
diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/civilClaimantNotification/sendSpokespersonAssignedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/civilClaimantNotification/sendSpokespersonAssignedNotifications.spec.ts
new file mode 100644
index 000000000000..42bf7c2a0ea8
--- /dev/null
+++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/civilClaimantNotification/sendSpokespersonAssignedNotifications.spec.ts
@@ -0,0 +1,167 @@
+import { uuid } from 'uuidv4'
+
+import { EmailService } from '@island.is/email-service'
+import { ConfigType } from '@island.is/nest/config'
+
+import { DEFENDER_INDICTMENT_ROUTE } from '@island.is/judicial-system/consts'
+import {
+ CaseType,
+ CivilClaimantNotificationType,
+} from '@island.is/judicial-system/types'
+
+import { createTestingNotificationModule } from '../../createTestingNotificationModule'
+
+import { Case } from '../../../../case'
+import { CivilClaimant } from '../../../../defendant'
+import { CivilClaimantNotificationDto } from '../../../dto/civilClaimantNotification.dto'
+import { DeliverResponse } from '../../../models/deliver.response'
+import { Notification } from '../../../models/notification.model'
+import { notificationModuleConfig } from '../../../notification.config'
+
+jest.mock('../../../../../factories')
+
+interface Then {
+ result: DeliverResponse
+ error: Error
+}
+
+type GivenWhenThen = (
+ caseId: string,
+ civilClaimantId: string,
+ theCase: Case,
+ civilClaimant: CivilClaimant,
+ notificationDto: CivilClaimantNotificationDto,
+) => Promise
+
+describe('InternalNotificationController - Send spokesperson assigned notifications', () => {
+ const caseId = uuid()
+ const civilClaimantId = uuid()
+ const court = { name: 'Héraðsdómur Reykjavíkur' } as Case['court']
+
+ let mockEmailService: EmailService
+ let mockConfig: ConfigType
+ let mockNotificationModel: typeof Notification
+ let givenWhenThen: GivenWhenThen
+
+ let civilClaimantNotificationDTO: CivilClaimantNotificationDto
+
+ beforeEach(async () => {
+ const {
+ emailService,
+ notificationConfig,
+ internalNotificationController,
+ notificationModel,
+ } = await createTestingNotificationModule()
+
+ civilClaimantNotificationDTO = {
+ type: CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
+ }
+
+ mockEmailService = emailService
+ mockConfig = notificationConfig
+ mockNotificationModel = notificationModel
+
+ givenWhenThen = async (
+ caseId: string,
+ civilClaimantId: string,
+ theCase: Case,
+ civilClaimant: CivilClaimant,
+ notificationDto: CivilClaimantNotificationDto,
+ ) => {
+ const then = {} as Then
+
+ try {
+ then.result =
+ await internalNotificationController.sendCivilClaimantNotification(
+ caseId,
+ civilClaimantId,
+ theCase,
+ civilClaimant,
+ notificationDto,
+ )
+ } catch (error) {
+ then.error = error as Error
+ }
+
+ return then
+ }
+ })
+
+ describe.each([
+ { isSpokespersonConfirmed: true, shouldSendEmail: true },
+ { isSpokespersonConfirmed: false, shouldSendEmail: false },
+ ])(
+ 'when sending a spokesperson assigned notification',
+ ({ isSpokespersonConfirmed, shouldSendEmail }) => {
+ const civilClaimant = {
+ id: civilClaimantId,
+ caseId,
+ isSpokespersonConfirmed,
+ spokespersonIsLawyer: true,
+ spokespersonNationalId: '1234567890',
+ spokespersonName: 'Ben 10',
+ spokespersonEmail: 'ben10@omnitrix.is',
+ } as CivilClaimant
+
+ beforeEach(async () => {
+ await givenWhenThen(
+ caseId,
+ civilClaimantId,
+ {
+ id: caseId,
+ court,
+ courtCaseNumber: 'R-123-456',
+ type: CaseType.INDICTMENT,
+ civilClaimants: [civilClaimant],
+ hasCivilClaims: true,
+ } as Case,
+ civilClaimant,
+ civilClaimantNotificationDTO,
+ )
+ })
+
+ test(`should ${
+ shouldSendEmail ? '' : 'not '
+ }send a spokesperson assigned notification`, async () => {
+ if (shouldSendEmail) {
+ expect(mockEmailService.sendEmail).toBeCalledTimes(1)
+ expect(mockEmailService.sendEmail).toBeCalledWith({
+ from: {
+ name: mockConfig.email.fromName,
+ address: mockConfig.email.fromEmail,
+ },
+ to: [
+ {
+ name: civilClaimant.spokespersonName,
+ address: civilClaimant.spokespersonEmail,
+ },
+ ],
+ replyTo: {
+ name: mockConfig.email.replyToName,
+ address: mockConfig.email.replyToEmail,
+ },
+ attachments: undefined,
+ subject: `Héraðsdómur Reykjavíkur - aðgangur að máli`,
+ html: expect.stringContaining(DEFENDER_INDICTMENT_ROUTE),
+ text: expect.stringContaining(
+ `Héraðsdómur Reykjavíkur hefur skráð þig lögmann einkaréttarkröfuhafa í máli R-123-456`,
+ ),
+ })
+ expect(mockNotificationModel.create).toHaveBeenCalledTimes(1)
+ expect(mockNotificationModel.create).toHaveBeenCalledWith({
+ caseId,
+ type: civilClaimantNotificationDTO.type,
+ recipients: [
+ {
+ address: civilClaimant.spokespersonEmail,
+ success: shouldSendEmail,
+ },
+ ],
+ })
+ } else {
+ expect(mockEmailService.sendEmail).not.toBeCalled()
+ }
+ })
+ },
+ )
+})
diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAdvocateAssignedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAdvocateAssignedNotifications.spec.ts
index eff36227b407..d439505025a9 100644
--- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAdvocateAssignedNotifications.spec.ts
+++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAdvocateAssignedNotifications.spec.ts
@@ -83,120 +83,6 @@ describe('InternalNotificationController - Send defender assigned notifications'
}
})
- describe('when the case has civil claims and the advocate is a lawyer', () => {
- const caseId = uuid()
- const civilClaimant = {
- hasSpokesperson: true,
- spokespersonNationalId: '1234567890',
- spokespersonEmail: 'recipient@gmail.com',
- spokespersonName: 'John Doe',
- spokespersonIsLawyer: true,
- }
- const theCase = {
- id: caseId,
- type: CaseType.INDICTMENT,
- court,
- courtCaseNumber: 'S-123/2022',
- civilClaimants: [civilClaimant],
- } as Case
-
- beforeEach(async () => {
- await givenWhenThen(caseId, theCase, notificationDTO)
- })
-
- it('should send correct email', () => {
- expect(mockEmailService.sendEmail).toHaveBeenCalledTimes(1)
- expect(mockEmailService.sendEmail).toHaveBeenCalledWith({
- from: {
- name: mockConfig.email.fromName,
- address: mockConfig.email.fromEmail,
- },
- to: [
- {
- name: civilClaimant.spokespersonName,
- address: civilClaimant.spokespersonEmail,
- },
- ],
- replyTo: {
- name: mockConfig.email.replyToName,
- address: mockConfig.email.replyToEmail,
- },
- attachments: undefined,
- subject: `Skráning í máli ${theCase.courtCaseNumber}`,
- text: expect.anything(), // same as html but stripped html tags
- html: `Héraðsdómur Reykjavíkur hefur skráð þig lögmann einkaréttarkröfuhafa í máli ${theCase.courtCaseNumber}.
Sjá nánar á yfirlitssíðu málsins í Réttarvörslugátt.`,
- })
- })
- })
-
- describe('when the case has civil claims and the advocate is a legal rights protector', () => {
- const caseId = uuid()
- const civilClaimant = {
- hasSpokesperson: true,
- spokespersonNationalId: '1234567890',
- spokespersonEmail: 'recipient@gmail.com',
- spokespersonName: 'John Doe',
- spokespersonIsLawyer: false,
- }
- const theCase = {
- id: caseId,
- type: CaseType.INDICTMENT,
- court,
- courtCaseNumber: 'S-123/2022',
- civilClaimants: [civilClaimant],
- } as Case
-
- beforeEach(async () => {
- await givenWhenThen(caseId, theCase, notificationDTO)
- })
-
- it('should send correct email', () => {
- expect(mockEmailService.sendEmail).toHaveBeenCalledTimes(1)
- expect(mockEmailService.sendEmail).toHaveBeenCalledWith({
- from: {
- name: mockConfig.email.fromName,
- address: mockConfig.email.fromEmail,
- },
- to: [
- {
- name: civilClaimant.spokespersonName,
- address: civilClaimant.spokespersonEmail,
- },
- ],
- replyTo: {
- name: mockConfig.email.replyToName,
- address: mockConfig.email.replyToEmail,
- },
- attachments: undefined,
- subject: `Skráning í máli ${theCase.courtCaseNumber}`,
- text: expect.anything(), // same as html but stripped html tags
- html: `Héraðsdómur Reykjavíkur hefur skráð þig réttargæslumann einkaréttarkröfuhafa í máli ${theCase.courtCaseNumber}.
Sjá nánar á yfirlitssíðu málsins í Réttarvörslugátt.`,
- })
- })
- })
-
- describe('when the case has civil claims and civil claimant does not have representation', () => {
- const caseId = uuid()
- const civilClaimant = {
- hasSpokesperson: false,
- }
- const theCase = {
- id: caseId,
- type: CaseType.INDICTMENT,
- court,
- courtCaseNumber: 'S-123/2022',
- civilClaimants: [civilClaimant],
- } as Case
-
- beforeEach(async () => {
- await givenWhenThen(caseId, theCase, notificationDTO)
- })
-
- it('should send correct email', () => {
- expect(mockEmailService.sendEmail).not.toHaveBeenCalled()
- })
- })
-
describe('when sending assigned defender notifications in a restriction case', () => {
const caseId = uuid()
const theCase = {
diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts
index 4cccc3b48615..c8650bc09256 100644
--- a/libs/judicial-system/message/src/lib/message.ts
+++ b/libs/judicial-system/message/src/lib/message.ts
@@ -30,6 +30,7 @@ export enum MessageType {
INSTITUTION_NOTIFICATION = 'INSTITUTION_NOTIFICATION',
NOTIFICATION_DISPATCH = 'NOTIFICATION_DISPATCH',
DEFENDANT_NOTIFICATION = 'DEFENDANT_NOTIFICATION',
+ CIVIL_CLAIMANT_NOTIFICATION = 'CIVIL_CLAIMANT_NOTIFICATION',
}
export const messageEndpoint: { [key in MessageType]: string } = {
@@ -66,6 +67,7 @@ export const messageEndpoint: { [key in MessageType]: string } = {
INSTITUTION_NOTIFICATION: 'institutionNotification',
NOTIFICATION_DISPATCH: 'notification/dispatch',
DEFENDANT_NOTIFICATION: 'defendantNotification',
+ CIVIL_CLAIMANT_NOTIFICATION: 'civilClaimantNotification',
}
export type Message = {
diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts
index 494e5067a5e2..4357ea6ad607 100644
--- a/libs/judicial-system/types/src/index.ts
+++ b/libs/judicial-system/types/src/index.ts
@@ -18,6 +18,7 @@ export {
InstitutionNotificationType,
NotificationDispatchType,
DefendantNotificationType,
+ CivilClaimantNotificationType,
notificationTypes,
} from './lib/notification'
export type { Institution } from './lib/institution'
diff --git a/libs/judicial-system/types/src/lib/notification.ts b/libs/judicial-system/types/src/lib/notification.ts
index 981ab499ff70..6c449ffc196e 100644
--- a/libs/judicial-system/types/src/lib/notification.ts
+++ b/libs/judicial-system/types/src/lib/notification.ts
@@ -30,6 +30,10 @@ export enum SubpoenaNotificationType {
SERVICE_FAILED = 'SERVICE_FAILED',
}
+export enum CivilClaimantNotificationType {
+ SPOKESPERSON_ASSIGNED = 'SPOKESPERSON_ASSIGNED',
+}
+
export enum InstitutionNotificationType {
INDICTMENTS_WAITING_FOR_CONFIRMATION = 'INDICTMENTS_WAITING_FOR_CONFIRMATION',
}
@@ -58,6 +62,7 @@ export enum NotificationType {
DEFENDER_ASSIGNED = DefendantNotificationType.DEFENDER_ASSIGNED,
SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL,
SERVICE_FAILED = SubpoenaNotificationType.SERVICE_FAILED,
+ SPOKESPERSON_ASSIGNED = CivilClaimantNotificationType.SPOKESPERSON_ASSIGNED,
INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION,
}