diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts index cc761d4b363a..595b696e2c28 100644 --- a/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts +++ b/apps/judicial-system/api/src/app/modules/defendant/models/defendant.model.ts @@ -8,6 +8,8 @@ import { SubpoenaType, } from '@island.is/judicial-system/types' +import { Subpoena } from './subpoena.model' + registerEnumType(Gender, { name: 'Gender' }) registerEnumType(DefendantPlea, { name: 'DefendantPlea' }) registerEnumType(ServiceRequirement, { name: 'ServiceRequirement' }) @@ -75,4 +77,7 @@ export class Defendant { @Field(() => SubpoenaType, { nullable: true }) readonly subpoenaType?: SubpoenaType + + @Field(() => [Subpoena], { nullable: true }) + readonly subpoenas?: Subpoena[] } diff --git a/apps/judicial-system/api/src/app/modules/defendant/models/subpoena.model.ts b/apps/judicial-system/api/src/app/modules/defendant/models/subpoena.model.ts new file mode 100644 index 000000000000..9a810b1edc10 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/defendant/models/subpoena.model.ts @@ -0,0 +1,31 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql' + +@ObjectType() +export class Subpoena { + @Field(() => ID) + readonly id!: string + + @Field(() => String, { nullable: true }) + created?: string + + @Field(() => String, { nullable: true }) + modified?: string + + @Field(() => String, { nullable: true }) + subpoenaId?: string + + @Field(() => Boolean, { nullable: true }) + acknowledged?: boolean + + @Field(() => String, { nullable: true }) + registeredBy?: string + + @Field(() => String, { nullable: true }) + comment?: string + + @Field(() => String, { nullable: true }) + arraignmentDate?: string + + @Field(() => String, { nullable: true }) + location?: string +} diff --git a/apps/judicial-system/api/src/app/modules/file/file.controller.ts b/apps/judicial-system/api/src/app/modules/file/file.controller.ts index 34e61e6ad93c..d278438871f3 100644 --- a/apps/judicial-system/api/src/app/modules/file/file.controller.ts +++ b/apps/judicial-system/api/src/app/modules/file/file.controller.ts @@ -177,27 +177,33 @@ export class FileController { ) } - @Get('subpoena/:defendantId') + @Get(['subpoena/:defendantId', 'subpoena/:defendantId/:subpoenaId']) @Header('Content-Type', 'application/pdf') getSubpoenaPdf( @Param('id') id: string, @Param('defendantId') defendantId: string, - @Query('arraignmentDate') arraignmentDate: string, - @Query('location') location: string, - @Query('subpoenaType') subpoenaType: SubpoenaType, + @Param('subpoenaId') subpoenaId: string, @CurrentHttpUser() user: User, @Req() req: Request, @Res() res: Response, + @Query('arraignmentDate') arraignmentDate?: string, + @Query('location') location?: string, + @Query('subpoenaType') subpoenaType?: SubpoenaType, ): Promise { this.logger.debug( `Getting the subpoena for defendant ${defendantId} of case ${id} as a pdf document`, ) + const subpoenaIdInjection = subpoenaId ? `/${subpoenaId}` : '' + const queryInjection = arraignmentDate + ? `?arraignmentDate=${arraignmentDate}&location=${location}&subpoenaType=${subpoenaType}` + : '' + return this.fileService.tryGetFile( user.id, AuditedAction.GET_SUBPOENA_PDF, id, - `defendant/${defendantId}/subpoena?arraignmentDate=${arraignmentDate}&location=${location}&subpoenaType=${subpoenaType}`, + `defendant/${defendantId}/subpoena${subpoenaIdInjection}${queryInjection}`, req, res, 'pdf', diff --git a/apps/judicial-system/backend/migrations/20240926131706-update-subpoena.js b/apps/judicial-system/backend/migrations/20240926131706-update-subpoena.js new file mode 100644 index 000000000000..f4e74d657c49 --- /dev/null +++ b/apps/judicial-system/backend/migrations/20240926131706-update-subpoena.js @@ -0,0 +1,61 @@ +'use strict' + +module.exports = { + up(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction((transaction) => + Promise.all([ + queryInterface.addColumn( + 'subpoena', + 'arraignment_date', + { + type: Sequelize.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + }, + { transaction }, + ), + queryInterface.addColumn( + 'subpoena', + 'location', + { type: Sequelize.STRING, allowNull: false, defaultValue: 'óþekkt' }, + { transaction }, + ), + queryInterface.changeColumn( + 'subpoena', + 'case_id', + { + type: Sequelize.UUID, + allowNull: false, + }, + { transaction }, + ), + ]).then(() => + queryInterface.sequelize.query( + `ALTER TABLE subpoena ALTER COLUMN arraignment_date DROP DEFAULT; + ALTER TABLE subpoena ALTER COLUMN location DROP DEFAULT;`, + { transaction }, + ), + ), + ) + }, + + down(queryInterface, Sequelize) { + return queryInterface.sequelize.transaction((transaction) => + Promise.all([ + queryInterface.removeColumn('subpoena', 'arraignment_date', { + transaction, + }), + queryInterface.removeColumn('subpoena', 'location', { transaction }), + queryInterface.changeColumn( + 'subpoena', + 'case_id', + { + type: Sequelize.UUID, + allowNull: true, + }, + { transaction }, + ), + ]), + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts index 4af8001053e8..95567e0bd7a4 100644 --- a/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/subpoenaPdf.ts @@ -7,11 +7,13 @@ import { formatDOB, lowercase, } from '@island.is/judicial-system/formatters' -import { DateType, SubpoenaType } from '@island.is/judicial-system/types' +import { SubpoenaType } from '@island.is/judicial-system/types' +import { nowFactory } from '../factories/date.factory' import { subpoena as strings } from '../messages' import { Case } from '../modules/case' import { Defendant } from '../modules/defendant' +import { Subpoena } from '../modules/subpoena' import { addConfirmation, addEmptyLines, @@ -27,6 +29,7 @@ export const createSubpoena = ( theCase: Case, defendant: Defendant, formatMessage: FormatMessage, + subpoena?: Subpoena, arraignmentDate?: Date, location?: string, subpoenaType?: SubpoenaType, @@ -43,15 +46,12 @@ export const createSubpoena = ( }) const sinc: Buffer[] = [] - const dateLog = theCase.dateLogs?.find( - (d) => d.dateType === DateType.ARRAIGNMENT_DATE, - ) doc.on('data', (chunk) => sinc.push(chunk)) setTitle(doc, formatMessage(strings.title)) - if (dateLog) { + if (subpoena) { addEmptyLines(doc, 5) } @@ -59,12 +59,12 @@ export const createSubpoena = ( addNormalRightAlignedText( doc, - `${formatDate(new Date(dateLog?.modified ?? new Date()), 'PPP')}`, + `${formatDate(new Date(subpoena?.created ?? nowFactory()), 'PPP')}`, 'Times-Roman', ) - arraignmentDate = arraignmentDate ?? dateLog?.date - location = location ?? dateLog?.location + arraignmentDate = arraignmentDate ?? subpoena?.arraignmentDate + location = location ?? subpoena?.location subpoenaType = subpoenaType ?? defendant.subpoenaType if (theCase.court?.name) { @@ -154,12 +154,12 @@ export const createSubpoena = ( addFooter(doc) - if (dateLog) { + if (subpoena) { addConfirmation(doc, { actor: theCase.judge?.name || '', title: theCase.judge?.title, institution: theCase.judge?.institution?.name || '', - date: dateLog.created, + date: subpoena.created, }) } diff --git a/apps/judicial-system/backend/src/app/modules/case/case.module.ts b/apps/judicial-system/backend/src/app/modules/case/case.module.ts index 10d099b3068b..139bff373cfe 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.module.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.module.ts @@ -15,6 +15,7 @@ import { FileModule, IndictmentCountModule, PoliceModule, + SubpoenaModule, UserModule, } from '../index' import { Case } from './models/case.model' @@ -35,6 +36,7 @@ import { PdfService } from './pdf.service' CmsTranslationsModule, MessageModule, forwardRef(() => DefendantModule), + forwardRef(() => SubpoenaModule), forwardRef(() => UserModule), forwardRef(() => FileModule), forwardRef(() => IndictmentCountModule), diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 65ad33c4d2ee..06c0dcee6ea1 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -62,6 +62,7 @@ import { CaseFile, FileService } from '../file' import { IndictmentCount } from '../indictment-count' import { Institution } from '../institution' import { Notification } from '../notification' +import { Subpoena, SubpoenaService } from '../subpoena' import { User } from '../user' import { CreateCaseDto } from './dto/createCase.dto' import { getCasesQueryFilter } from './filters/cases.filter' @@ -271,7 +272,22 @@ export const include: Includeable[] = [ ], }, { model: Case, as: 'childCase' }, - { model: Defendant, as: 'defendants' }, + { + model: Defendant, + as: 'defendants', + required: false, + order: [['created', 'ASC']], + include: [ + { + model: Subpoena, + as: 'subpoenas', + required: false, + order: [['created', 'DESC']], + separate: true, + }, + ], + separate: true, + }, { model: CivilClaimant, as: 'civilClaimants' }, { model: IndictmentCount, as: 'indictmentCounts' }, { @@ -340,7 +356,6 @@ export const include: Includeable[] = [ ] export const order: OrderItem[] = [ - [{ model: Defendant, as: 'defendants' }, 'created', 'ASC'], [{ model: CivilClaimant, as: 'civilClaimants' }, 'created', 'ASC'], [{ model: IndictmentCount, as: 'indictmentCounts' }, 'created', 'ASC'], [{ model: DateLog, as: 'dateLogs' }, 'created', 'DESC'], @@ -412,6 +427,7 @@ export class CaseService { @Inject(caseModuleConfig.KEY) private readonly config: ConfigType, private readonly defendantService: DefendantService, + private readonly subpoenaService: SubpoenaService, private readonly fileService: FileService, private readonly awsS3Service: AwsS3Service, private readonly courtService: CourtService, @@ -1146,29 +1162,42 @@ export class CaseService { private addMessagesForNewCourtDateToQueue( theCase: Case, user: TUser, - arraignmentDateChanged: boolean, ): Promise { - const messages: Message[] = [ + return this.messageService.sendMessagesToQueue([ { type: MessageType.NOTIFICATION, user, caseId: theCase.id, body: { type: NotificationType.COURT_DATE }, }, - ] + ]) + } - if (arraignmentDateChanged) { - theCase.defendants?.forEach((defendant) => { - messages.push({ - type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, - user, - caseId: theCase.id, - elementId: defendant.id, - }) - }) + private addMessagesForNewSubpoenasToQueue( + theCase: Case, + updatedCase: Case, + user: TUser, + ) { + const messages = updatedCase.defendants + ?.filter( + (updatedDefendant) => + theCase.defendants?.find( + (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 ?? '', + ], + })) + + if (messages && messages.length > 0) { + return this.messageService.sendMessagesToQueue(messages) } - - return this.messageService.sendMessagesToQueue(messages) } private async addMessagesForUpdatedCaseToQueue( @@ -1344,12 +1373,10 @@ export class CaseService { if (arraignmentDateChanged || courtDateChanged) { // New arraignment date or new court date - await this.addMessagesForNewCourtDateToQueue( - updatedCase, - user, - Boolean(arraignmentDateChanged), - ) + await this.addMessagesForNewCourtDateToQueue(updatedCase, user) } + + await this.addMessagesForNewSubpoenasToQueue(theCase, updatedCase, user) } } @@ -1628,8 +1655,18 @@ export class CaseService { isIndictmentCase(theCase.type) && update.state === CaseState.DRAFT && theCase.state === CaseState.RECEIVED - const completingIndictmentCase = - isIndictmentCase(theCase.type) && update.state === CaseState.COMPLETED + const completingIndictmentCaseWithoutRuling = + isIndictmentCase(theCase.type) && + update.state === CaseState.COMPLETED && + theCase.indictmentRulingDecision && + [ + CaseIndictmentRulingDecision.FINE, + CaseIndictmentRulingDecision.CANCELLATION, + CaseIndictmentRulingDecision.MERGE, + ].includes(theCase.indictmentRulingDecision) + const updatedArraignmentDate = update.arraignmentDate + const schedulingNewArraignmentDateForIndictmentCase = + isIndictmentCase(theCase.type) && Boolean(updatedArraignmentDate) return this.sequelize .transaction(async (transaction) => { @@ -1646,24 +1683,22 @@ export class CaseService { await this.handleCommentUpdates(theCase, update, transaction) await this.handleEventLogs(theCase, update, user, transaction) - if (Object.keys(update).length === 0) { - return - } - - const [numberOfAffectedRows] = await this.caseModel.update(update, { - where: { id: theCase.id }, - transaction, - }) + if (Object.keys(update).length > 0) { + const [numberOfAffectedRows] = await this.caseModel.update(update, { + where: { id: theCase.id }, + transaction, + }) - if (numberOfAffectedRows > 1) { - // Tolerate failure, but log error - this.logger.error( - `Unexpected number of rows (${numberOfAffectedRows}) affected when updating case ${theCase.id}`, - ) - } else if (numberOfAffectedRows < 1) { - throw new InternalServerErrorException( - `Could not update case ${theCase.id}`, - ) + if (numberOfAffectedRows > 1) { + // Tolerate failure, but log error + this.logger.error( + `Unexpected number of rows (${numberOfAffectedRows}) affected when updating case ${theCase.id}`, + ) + } else if (numberOfAffectedRows < 1) { + throw new InternalServerErrorException( + `Could not update case ${theCase.id}`, + ) + } } // Update police case numbers of case files if necessary @@ -1689,22 +1724,34 @@ export class CaseService { ) } - if ( - completingIndictmentCase && - theCase.indictmentRulingDecision && - [ - CaseIndictmentRulingDecision.FINE, - CaseIndictmentRulingDecision.CANCELLATION, - ].includes(theCase.indictmentRulingDecision) - ) { + // Remove uploaded ruling files if an indictment case is completed without a ruling + if (completingIndictmentCaseWithoutRuling && theCase.caseFiles) { await Promise.all( theCase.caseFiles - ?.filter( + .filter( (caseFile) => caseFile.category === CaseFileCategory.RULING, ) - ?.map((caseFile) => + .map((caseFile) => this.fileService.deleteCaseFile(theCase, caseFile, transaction), - ) ?? [], + ), + ) + } + + // Create new subpoeans if scheduling a new arraignment date for an indictment case + if ( + schedulingNewArraignmentDateForIndictmentCase && + theCase.defendants + ) { + await Promise.all( + theCase.defendants.map((defendant) => + this.subpoenaService.createSubpoena( + defendant.id, + theCase.id, + transaction, + updatedArraignmentDate?.date, + updatedArraignmentDate?.location, + ), + ), ) } }) diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts index 4ef8b7b5a247..26377502db54 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.service.ts @@ -58,7 +58,7 @@ import { CaseFile, FileService } from '../file' import { IndictmentCount, IndictmentCountService } from '../indictment-count' import { Institution } from '../institution' import { PoliceDocument, PoliceDocumentType, PoliceService } from '../police' -import { Subpoena } from '../subpoena/models/subpoena.model' +import { Subpoena } from '../subpoena' import { User, UserService } from '../user' import { InternalCreateCaseDto } from './dto/internalCreateCase.dto' import { archiveFilter } from './filters/case.archiveFilter' diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts index fa2e9b5bbaeb..efeaf459c19a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.service.ts @@ -38,6 +38,7 @@ import { } from '../file' import { IndictmentCount } from '../indictment-count' import { Institution } from '../institution' +import { Subpoena } from '../subpoena' import { User } from '../user' import { Case } from './models/case.model' import { CaseString } from './models/caseString.model' @@ -170,7 +171,22 @@ export const include: Includeable[] = [ }, { model: Case, as: 'parentCase', attributes }, { model: Case, as: 'childCase', attributes }, - { model: Defendant, as: 'defendants' }, + { + model: Defendant, + as: 'defendants', + required: false, + order: [['created', 'ASC']], + include: [ + { + model: Subpoena, + as: 'subpoenas', + required: false, + order: [['created', 'DESC']], + separate: true, + }, + ], + separate: true, + }, { model: IndictmentCount, as: 'indictmentCounts' }, { model: CivilClaimant, as: 'civilClaimants' }, { @@ -255,7 +271,6 @@ export const include: Includeable[] = [ ] export const order: OrderItem[] = [ - [{ model: Defendant, as: 'defendants' }, 'created', 'ASC'], [{ model: IndictmentCount, as: 'indictmentCounts' }, 'created', 'ASC'], [{ model: CivilClaimant, as: 'civilClaimants' }, 'created', 'ASC'], [{ model: DateLog, as: 'dateLogs' }, 'created', 'DESC'], diff --git a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts index 6b956856ebb1..10065ea3bdb6 100644 --- a/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/pdf.service.ts @@ -33,6 +33,7 @@ import { } from '../../formatters' import { AwsS3Service } from '../aws-s3' import { Defendant } from '../defendant' +import { Subpoena } from '../subpoena' import { UserService } from '../user' import { Case } from './models/case.model' @@ -292,6 +293,7 @@ export class PdfService { async getSubpoenaPdf( theCase: Case, defendant: Defendant, + subpoena?: Subpoena, arraignmentDate?: Date, location?: string, subpoenaType?: SubpoenaType, @@ -302,6 +304,7 @@ export class PdfService { theCase, defendant, this.formatMessage, + subpoena, arraignmentDate, location, subpoenaType, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts index 932319cac89e..4939ca40beb3 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/update.spec.ts @@ -875,10 +875,16 @@ describe('CaseController - Update', () => { describe('indictment arraignment date updated', () => { const arraignmentDate = { date: new Date(), location: uuid() } const caseToUpdate = { arraignmentDate } + const subpoenaId1 = uuid() + const subpoenaId2 = uuid() const updatedCase = { ...theCase, type: CaseType.INDICTMENT, dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, ...arraignmentDate }], + defendants: [ + { id: defendantId1, subpoenas: [{ id: subpoenaId1 }] }, + { id: defendantId2, subpoenas: [{ id: subpoenaId2 }] }, + ], } beforeEach(async () => { @@ -900,17 +906,19 @@ describe('CaseController - Update', () => { caseId, body: { type: NotificationType.COURT_DATE }, }, + ]) + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ { type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, user, caseId: theCase.id, - elementId: defendantId1, + elementId: [defendantId1, subpoenaId1], }, { type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, user, caseId: theCase.id, - elementId: defendantId2, + elementId: [defendantId2, subpoenaId2], }, ]) }) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts index cb7ab62524ed..83edd4cd8b1e 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.controller.ts @@ -1,17 +1,11 @@ -import { Response } from 'express' - import { Body, Controller, Delete, - Get, - Header, Inject, Param, Patch, Post, - Query, - Res, UseGuards, } from '@nestjs/common' import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger' @@ -25,12 +19,7 @@ import { RolesGuard, RolesRules, } from '@island.is/judicial-system/auth' -import { - indictmentCases, - ServiceRequirement, - SubpoenaType, - type User, -} from '@island.is/judicial-system/types' +import { ServiceRequirement, type User } from '@island.is/judicial-system/types' import { districtCourtAssistantRule, @@ -40,15 +29,7 @@ import { prosecutorRule, publicProsecutorStaffRule, } from '../../guards' -import { - Case, - CaseExistsGuard, - CaseReadGuard, - CaseTypeGuard, - CaseWriteGuard, - CurrentCase, - PdfService, -} from '../case' +import { Case, CaseExistsGuard, CaseWriteGuard, CurrentCase } from '../case' import { CreateDefendantDto } from './dto/createDefendant.dto' import { UpdateDefendantDto } from './dto/updateDefendant.dto' import { CurrentDefendant } from './guards/defendant.decorator' @@ -62,7 +43,6 @@ import { DefendantService } from './defendant.service' @ApiTags('defendants') export class DefendantController { constructor( - private readonly pdfService: PdfService, private readonly defendantService: DefendantService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -148,49 +128,4 @@ export class DefendantController { return { deleted } } - - @UseGuards( - CaseExistsGuard, - new CaseTypeGuard(indictmentCases), - CaseReadGuard, - DefendantExistsGuard, - ) - @RolesRules( - prosecutorRule, - prosecutorRepresentativeRule, - publicProsecutorStaffRule, - districtCourtJudgeRule, - districtCourtRegistrarRule, - districtCourtAssistantRule, - ) - @Get(':defendantId/subpoena') - @Header('Content-Type', 'application/pdf') - @ApiOkResponse({ - content: { 'application/pdf': {} }, - description: 'Gets the subpoena for a given defendant as a pdf document', - }) - async getSubpoenaPdf( - @Param('caseId') caseId: string, - @Param('defendantId') defendantId: string, - @CurrentCase() theCase: Case, - @CurrentDefendant() defendant: Defendant, - @Res() res: Response, - @Query('arraignmentDate') arraignmentDate?: Date, - @Query('location') location?: string, - @Query('subpoenaType') subpoenaType?: SubpoenaType, - ): Promise { - this.logger.debug( - `Getting the subpoena for defendant ${defendantId} of case ${caseId} as a pdf document`, - ) - - const pdf = await this.pdfService.getSubpoenaPdf( - theCase, - defendant, - arraignmentDate, - location, - subpoenaType, - ) - - res.end(pdf) - } } diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.module.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.module.ts index 7fc42f0f16fa..a7cb4fca9e0f 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.module.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.module.ts @@ -5,7 +5,6 @@ import { MessageModule } from '@island.is/judicial-system/message' import { CaseModule } from '../case/case.module' import { CourtModule } from '../court/court.module' -import { Subpoena } from '../subpoena/models/subpoena.model' import { CivilClaimant } from './models/civilClaimant.model' import { Defendant } from './models/defendant.model' import { CivilClaimantController } from './civilClaimant.controller' @@ -13,19 +12,17 @@ import { CivilClaimantService } from './civilClaimant.service' import { DefendantController } from './defendant.controller' import { DefendantService } from './defendant.service' import { InternalDefendantController } from './internalDefendant.controller' -import { LimitedAccessDefendantController } from './limitedAccessDefendant.controller' @Module({ imports: [ MessageModule, forwardRef(() => CourtModule), forwardRef(() => CaseModule), - SequelizeModule.forFeature([Defendant, CivilClaimant, Subpoena]), + SequelizeModule.forFeature([Defendant, CivilClaimant]), ], controllers: [ DefendantController, InternalDefendantController, - LimitedAccessDefendantController, CivilClaimantController, ], providers: [DefendantService, CivilClaimantService], 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 3839cdb96469..ed6753c1f764 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/index.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/index.ts @@ -1,3 +1,5 @@ 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' 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 8cb18eb62706..3b18ac9b8c8c 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 @@ -10,36 +10,29 @@ import { } from '@island.is/judicial-system/auth' import { MessageService } from '@island.is/judicial-system/message' -import { CaseService, PdfService } from '../../case' +import { CaseService } from '../../case' import { CourtService } from '../../court' import { UserService } from '../../user' import { DefendantController } from '../defendant.controller' import { DefendantService } from '../defendant.service' import { InternalDefendantController } from '../internalDefendant.controller' -import { LimitedAccessDefendantController } from '../limitedAccessDefendant.controller' import { Defendant } from '../models/defendant.model' jest.mock('@island.is/judicial-system/message') jest.mock('../../user/user.service') jest.mock('../../court/court.service') jest.mock('../../case/case.service') -jest.mock('../../case/pdf.service') export const createTestingDefendantModule = async () => { const defendantModule = await Test.createTestingModule({ imports: [ConfigModule.forRoot({ load: [sharedAuthModuleConfig] })], - controllers: [ - DefendantController, - InternalDefendantController, - LimitedAccessDefendantController, - ], + controllers: [DefendantController, InternalDefendantController], providers: [ SharedAuthModule, MessageService, UserService, CourtService, CaseService, - PdfService, { provide: LOGGER_PROVIDER, useValue: { @@ -69,8 +62,6 @@ export const createTestingDefendantModule = async () => { const courtService = defendantModule.get(CourtService) - const pdfService = defendantModule.get(PdfService) - const defendantModel = await defendantModule.resolve( getModelToken(Defendant), ) @@ -86,22 +77,15 @@ export const createTestingDefendantModule = async () => { InternalDefendantController, ) - const limitedAccessDefendantController = - defendantModule.get( - LimitedAccessDefendantController, - ) - defendantModule.close() return { messageService, userService, courtService, - pdfService, defendantModel, defendantService, defendantController, internalDefendantController, - limitedAccessDefendantController, } } diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfGuards.spec.ts deleted file mode 100644 index 7fb9d19876bb..000000000000 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfGuards.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { indictmentCases } from '@island.is/judicial-system/types' - -import { CaseExistsGuard, CaseReadGuard, CaseTypeGuard } from '../../../case' -import { DefendantController } from '../../defendant.controller' -import { DefendantExistsGuard } from '../../guards/defendantExists.guard' - -describe('DefendantController - Get custody notice pdf guards', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let guards: any[] - - beforeEach(() => { - guards = Reflect.getMetadata( - '__guards__', - DefendantController.prototype.getSubpoenaPdf, - ) - }) - - it('should have the right guard configuration', () => { - expect(guards).toHaveLength(4) - expect(new guards[0]()).toBeInstanceOf(CaseExistsGuard) - expect(guards[1]).toBeInstanceOf(CaseTypeGuard) - expect(guards[1]).toEqual({ - allowedCaseTypes: indictmentCases, - }) - expect(new guards[2]()).toBeInstanceOf(CaseReadGuard) - expect(new guards[3]()).toBeInstanceOf(DefendantExistsGuard) - }) -}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts b/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts index 0280c3f51f99..624bd2d361c1 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/guards/subpoenaExists.guard.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common' +import { Defendant } from '../../defendant' import { SubpoenaService } from '../subpoena.service' @Injectable() @@ -20,8 +21,41 @@ export class SubpoenaExistsGuard implements CanActivate { throw new BadRequestException('Missing subpoena id') } - request.subpoena = await this.subpoenaService.findBySubpoenaId(subpoenaId) + const defendant: Defendant = request.defendant + + if (!defendant) { + request.subpoena = await this.subpoenaService.findBySubpoenaId(subpoenaId) + + return true + } + + const subpoena = defendant.subpoenas?.find( + (subpoena) => subpoena.id === subpoenaId, + ) + + if (!subpoena) { + throw new BadRequestException( + `Subpoena ${subpoenaId} of defendant ${defendant.id} does not exist`, + ) + } + + request.subpoena = subpoena return true } } + +@Injectable() +export class SubpoenaExistsOptionalGuard extends SubpoenaExistsGuard { + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest() + + const subpoenaId = request.params.subpoenaId + + if (!subpoenaId) { + return true + } + + return super.canActivate(context) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/index.ts b/apps/judicial-system/backend/src/app/modules/subpoena/index.ts new file mode 100644 index 000000000000..828b617f4f30 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/index.ts @@ -0,0 +1,2 @@ +export { SubpoenaService } from './subpoena.service' +export { Subpoena } from './models/subpoena.model' diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts index a0f909375a80..411c8f3e4ef3 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/internalSubpoena.controller.ts @@ -1,5 +1,3 @@ -import { Base64 } from 'js-base64' - import { Body, Controller, @@ -22,12 +20,7 @@ import { } from '@island.is/judicial-system/message' import { indictmentCases } from '@island.is/judicial-system/types' -import { - CaseExistsGuard, - CaseTypeGuard, - CurrentCase, - PdfService, -} from '../case' +import { CaseExistsGuard, CaseTypeGuard, CurrentCase } from '../case' import { Case } from '../case/models/case.model' import { CurrentDefendant } from '../defendant/guards/defendant.decorator' import { DefendantExistsGuard } from '../defendant/guards/defendantExists.guard' @@ -46,7 +39,6 @@ import { SubpoenaService } from './subpoena.service' export class InternalSubpoenaController { constructor( private readonly subpoenaService: SubpoenaService, - private readonly pdfService: PdfService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -77,11 +69,12 @@ export class InternalSubpoenaController { CaseExistsGuard, new CaseTypeGuard(indictmentCases), DefendantExistsGuard, + SubpoenaExistsGuard, ) @Post( `case/:caseId/${ messageEndpoint[MessageType.DELIVERY_TO_POLICE_SUBPOENA] - }/:defendantId`, + }/:defendantId/:subpoenaId`, ) @ApiOkResponse({ type: DeliverResponse, @@ -90,20 +83,20 @@ export class InternalSubpoenaController { async deliverSubpoenaToPolice( @Param('caseId') caseId: string, @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, @CurrentCase() theCase: Case, @CurrentDefendant() defendant: Defendant, + @CurrentSubpoena() subpoena: Subpoena, @Body() deliverDto: DeliverDto, ): Promise { this.logger.debug( - `Delivering subpoena ${caseId} to police for defendant ${defendantId}`, + `Delivering subpoena ${subpoenaId} to police for defendant ${defendantId} of case ${caseId}`, ) - const pdf = await this.pdfService.getSubpoenaPdf(theCase, defendant) - return await this.subpoenaService.deliverSubpoenaToPolice( theCase, defendant, - Base64.btoa(pdf.toString('binary')), + subpoena, deliverDto.user, ) } diff --git a/apps/judicial-system/backend/src/app/modules/defendant/limitedAccessDefendant.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts similarity index 70% rename from apps/judicial-system/backend/src/app/modules/defendant/limitedAccessDefendant.controller.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts index d2e79ab2ef40..9a4c195e465e 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/limitedAccessDefendant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/limitedAccessSubpoena.controller.ts @@ -31,11 +31,15 @@ import { CurrentCase, PdfService, } from '../case' -import { CurrentDefendant } from './guards/defendant.decorator' -import { DefendantExistsGuard } from './guards/defendantExists.guard' -import { Defendant } from './models/defendant.model' +import { CurrentDefendant, Defendant, DefendantExistsGuard } from '../defendant' +import { CurrentSubpoena } from './guards/subpoena.decorator' +import { SubpoenaExistsOptionalGuard } from './guards/subpoenaExists.guard' +import { Subpoena } from './models/subpoena.model' -@Controller('api/case/:caseId/limitedAccess/defendant/:defendantId/subpoena') +@Controller([ + 'api/case/:caseId/limitedAccess/defendant/:defendantId/subpoena', + 'api/case/:caseId/limitedAccess/defendant/:defendantId/subpoena/:subpoenaId', +]) @UseGuards( JwtAuthGuard, RolesGuard, @@ -43,9 +47,10 @@ import { Defendant } from './models/defendant.model' new CaseTypeGuard(indictmentCases), CaseReadGuard, DefendantExistsGuard, + SubpoenaExistsOptionalGuard, ) @ApiTags('limited access defendants') -export class LimitedAccessDefendantController { +export class LimitedAccessSubpoenaController { constructor( private readonly pdfService: PdfService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, @@ -61,20 +66,25 @@ export class LimitedAccessDefendantController { async getSubpoenaPdf( @Param('caseId') caseId: string, @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, @CurrentCase() theCase: Case, @CurrentDefendant() defendant: Defendant, + @CurrentSubpoena() subpoena: Subpoena, @Res() res: Response, @Query('arraignmentDate') arraignmentDate?: Date, @Query('location') location?: string, @Query('subpoenaType') subpoenaType?: SubpoenaType, ): Promise { this.logger.debug( - `Getting the subpoena for defendant ${defendantId} of case ${caseId} as a pdf document`, + `Getting subpoena ${ + subpoenaId ?? 'draft' + } for defendant ${defendantId} of case ${caseId} as a pdf document`, ) const pdf = await this.pdfService.getSubpoenaPdf( theCase, defendant, + subpoena, arraignmentDate, location, subpoenaType, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/models/subpoena.model.ts b/apps/judicial-system/backend/src/app/modules/subpoena/models/subpoena.model.ts index 5a540fd3af27..4d4f05a53b67 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/models/subpoena.model.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/models/subpoena.model.ts @@ -49,9 +49,9 @@ export class Subpoena extends Model { defendant?: Defendant @ForeignKey(() => Case) - @Column({ type: DataType.UUID, allowNull: true }) + @Column({ type: DataType.UUID, allowNull: false }) @ApiProperty({ type: String }) - caseId?: string + caseId!: string @BelongsTo(() => Case, 'caseId') @ApiPropertyOptional({ type: Case }) @@ -59,7 +59,7 @@ export class Subpoena extends Model { @Column({ type: DataType.BOOLEAN, allowNull: true }) @ApiPropertyOptional({ type: Boolean }) - acknowledged?: string + acknowledged?: boolean @Column({ type: DataType.STRING, allowNull: true }) @ApiPropertyOptional({ type: String }) @@ -68,4 +68,12 @@ export class Subpoena extends Model { @Column({ type: DataType.TEXT, allowNull: true }) @ApiPropertyOptional({ type: String }) comment?: string + + @Column({ type: DataType.DATE, allowNull: false }) + @ApiProperty({ type: Date }) + arraignmentDate!: Date + + @Column({ type: DataType.STRING, allowNull: false }) + @ApiProperty({ type: String }) + location!: string } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts new file mode 100644 index 000000000000..0a4eea819bcb --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts @@ -0,0 +1,110 @@ +import { Response } from 'express' + +import { + Controller, + Get, + Header, + Inject, + Param, + Query, + Res, + UseGuards, +} from '@nestjs/common' +import { ApiOkResponse, ApiTags } from '@nestjs/swagger' + +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' + +import { + JwtAuthGuard, + RolesGuard, + RolesRules, +} from '@island.is/judicial-system/auth' +import { indictmentCases, SubpoenaType } from '@island.is/judicial-system/types' + +import { + districtCourtAssistantRule, + districtCourtJudgeRule, + districtCourtRegistrarRule, + prosecutorRepresentativeRule, + prosecutorRule, + publicProsecutorStaffRule, +} from '../../guards' +import { + Case, + CaseExistsGuard, + CaseReadGuard, + CaseTypeGuard, + CurrentCase, + PdfService, +} from '../case' +import { Defendant } from '../defendant' +import { CurrentDefendant } from '../defendant/guards/defendant.decorator' +import { DefendantExistsGuard } from '../defendant/guards/defendantExists.guard' +import { CurrentSubpoena } from './guards/subpoena.decorator' +import { SubpoenaExistsOptionalGuard } from './guards/subpoenaExists.guard' +import { Subpoena } from './models/subpoena.model' + +@UseGuards( + JwtAuthGuard, + RolesGuard, + CaseExistsGuard, + new CaseTypeGuard(indictmentCases), + CaseReadGuard, + DefendantExistsGuard, + SubpoenaExistsOptionalGuard, +) +@Controller([ + 'api/case/:caseId/defendant/:defendantId/subpoena', + 'api/case/:caseId/defendant/:defendantId/subpoena/:subpoenaId', +]) +@ApiTags('subpoenas') +export class SubpoenaController { + constructor( + private readonly pdfService: PdfService, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + ) {} + + @RolesRules( + prosecutorRule, + prosecutorRepresentativeRule, + publicProsecutorStaffRule, + districtCourtJudgeRule, + districtCourtRegistrarRule, + districtCourtAssistantRule, + ) + @Get() + @Header('Content-Type', 'application/pdf') + @ApiOkResponse({ + content: { 'application/pdf': {} }, + description: 'Gets the subpoena for a given defendant as a pdf document', + }) + async getSubpoenaPdf( + @Param('caseId') caseId: string, + @Param('defendantId') defendantId: string, + @Param('subpoenaId') subpoenaId: string, + @CurrentCase() theCase: Case, + @CurrentDefendant() defendant: Defendant, + @Res() res: Response, + @CurrentSubpoena() subpoena?: Subpoena, + @Query('arraignmentDate') arraignmentDate?: Date, + @Query('location') location?: string, + @Query('subpoenaType') subpoenaType?: SubpoenaType, + ): Promise { + this.logger.debug( + `Getting subpoena ${ + subpoenaId ?? 'draft' + } for defendant ${defendantId} of case ${caseId} as a pdf document`, + ) + + const pdf = await this.pdfService.getSubpoenaPdf( + theCase, + defendant, + subpoena, + arraignmentDate, + location, + subpoenaType, + ) + + res.end(pdf) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts index 1f40a7844f60..260e2154cfb5 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts @@ -2,21 +2,26 @@ import { forwardRef, Module } from '@nestjs/common' import { SequelizeModule } from '@nestjs/sequelize' import { CaseModule } from '../case/case.module' -import { DefendantModule } from '../defendant/defendant.module' import { Defendant } from '../defendant/models/defendant.model' import { PoliceModule } from '../police/police.module' import { Subpoena } from './models/subpoena.model' import { InternalSubpoenaController } from './internalSubpoena.controller' +import { LimitedAccessSubpoenaController } from './limitedAccessSubpoena.controller' +import { SubpoenaController } from './subpoena.controller' import { SubpoenaService } from './subpoena.service' @Module({ imports: [ forwardRef(() => CaseModule), - forwardRef(() => DefendantModule), forwardRef(() => PoliceModule), SequelizeModule.forFeature([Subpoena, Defendant]), ], - controllers: [InternalSubpoenaController], + controllers: [ + SubpoenaController, + InternalSubpoenaController, + LimitedAccessSubpoenaController, + ], providers: [SubpoenaService], + exports: [SubpoenaService], }) export class SubpoenaModule {} diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts index c28cf4ec4875..aa2fe114ef7f 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.service.ts @@ -1,3 +1,4 @@ +import { Base64 } from 'js-base64' import { Includeable, Sequelize } from 'sequelize' import { Transaction } from 'sequelize/types' @@ -10,6 +11,7 @@ import { LOGGER_PROVIDER } from '@island.is/logging' import type { User } from '@island.is/judicial-system/types' import { Case } from '../case/models/case.model' +import { PdfService } from '../case/pdf.service' import { Defendant } from '../defendant/models/defendant.model' import { PoliceService } from '../police' import { UpdateSubpoenaDto } from './dto/updateSubpoena.dto' @@ -26,16 +28,28 @@ export class SubpoenaService { @InjectConnection() private readonly sequelize: Sequelize, @InjectModel(Subpoena) private readonly subpoenaModel: typeof Subpoena, @InjectModel(Defendant) private readonly defendantModel: typeof Defendant, + private readonly pdfService: PdfService, @Inject(forwardRef(() => PoliceService)) private readonly policeService: PoliceService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} - async createSubpoena(defendant: Defendant): Promise { - return await this.subpoenaModel.create({ - defendantId: defendant.id, - caseId: defendant.caseId, - }) + async createSubpoena( + defendantId: string, + caseId: string, + transaction: Transaction, + arraignmentDate?: Date, + location?: string, + ): Promise { + return this.subpoenaModel.create( + { + defendantId, + caseId, + arraignmentDate, + location, + }, + { transaction }, + ) } async update( @@ -108,21 +122,26 @@ export class SubpoenaService { async deliverSubpoenaToPolice( theCase: Case, defendant: Defendant, - subpoenaFile: string, + subpoena: Subpoena, user: User, ): Promise { try { - const subpoena = await this.createSubpoena(defendant) + const pdf = await this.pdfService.getSubpoenaPdf( + theCase, + defendant, + subpoena, + ) const createdSubpoena = await this.policeService.createSubpoena( theCase, defendant, - subpoenaFile, + Base64.btoa(pdf.toString('binary')), user, ) if (!createdSubpoena) { this.logger.error('Failed to create subpoena file for police') + return { delivered: false } } @@ -135,6 +154,7 @@ export class SubpoenaService { return { delivered: true } } catch (error) { this.logger.error('Error delivering subpoena to police', error) + return { delivered: false } } } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts new file mode 100644 index 000000000000..a931f5fd7bfd --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts @@ -0,0 +1,110 @@ +import { Sequelize } from 'sequelize-typescript' + +import { getModelToken } from '@nestjs/sequelize' +import { Test } from '@nestjs/testing' + +import { LOGGER_PROVIDER } from '@island.is/logging' +import { ConfigModule } from '@island.is/nest/config' + +import { + SharedAuthModule, + sharedAuthModuleConfig, +} from '@island.is/judicial-system/auth' + +import { CaseService, PdfService } from '../../case' +import { Defendant } from '../../defendant' +import { PoliceService } from '../../police' +import { UserService } from '../../user' +import { InternalSubpoenaController } from '../internalSubpoena.controller' +import { LimitedAccessSubpoenaController } from '../limitedAccessSubpoena.controller' +import { Subpoena } from '../models/subpoena.model' +import { SubpoenaController } from '../subpoena.controller' +import { SubpoenaService } from '../subpoena.service' + +jest.mock('../../user/user.service') +jest.mock('../../case/case.service') +jest.mock('../../case/pdf.service') +jest.mock('../../police/police.service') + +export const createTestingSubpoenaModule = async () => { + const subpoenaModule = await Test.createTestingModule({ + imports: [ConfigModule.forRoot({ load: [sharedAuthModuleConfig] })], + controllers: [ + SubpoenaController, + InternalSubpoenaController, + LimitedAccessSubpoenaController, + ], + providers: [ + { provide: Sequelize, useValue: { transaction: jest.fn() } }, + SharedAuthModule, + UserService, + CaseService, + PdfService, + PoliceService, + { + provide: LOGGER_PROVIDER, + useValue: { + debug: jest.fn(), + info: jest.fn(), + error: jest.fn(), + }, + }, + { + provide: getModelToken(Subpoena), + useValue: { + findOne: jest.fn(), + findAll: jest.fn(), + create: jest.fn(), + update: jest.fn(), + destroy: jest.fn(), + findByPk: jest.fn(), + }, + }, + { + provide: getModelToken(Defendant), + useValue: { + findOne: jest.fn(), + findAll: jest.fn(), + create: jest.fn(), + update: jest.fn(), + destroy: jest.fn(), + findByPk: jest.fn(), + }, + }, + SubpoenaService, + ], + }).compile() + + const userService = subpoenaModule.get(UserService) + + const pdfService = subpoenaModule.get(PdfService) + + const subpoenaModel = await subpoenaModule.resolve( + getModelToken(Subpoena), + ) + + const subpoenaService = subpoenaModule.get(SubpoenaService) + + const subpoenaController = + subpoenaModule.get(SubpoenaController) + + const internalSubpoenaController = + subpoenaModule.get(InternalSubpoenaController) + + const limitedAccessSubpoenaController = + subpoenaModule.get( + LimitedAccessSubpoenaController, + ) + + subpoenaModule.close() + + return { + userService, + pdfService, + subpoenaModel, + subpoenaService, + subpoenaController, + internalSubpoenaController, + limitedAccessSubpoenaController, + } +} diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdf.spec.ts similarity index 68% rename from apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdf.spec.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdf.spec.ts index 632fc703f493..cda75ddd2fbe 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdf.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdf.spec.ts @@ -1,10 +1,10 @@ import { Response } from 'express' import { uuid } from 'uuidv4' -import { createTestingDefendantModule } from '../createTestingDefendantModule' +import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' import { Case, PdfService } from '../../../case' -import { Defendant } from '../../models/defendant.model' +import { Subpoena } from '../../models/subpoena.model' interface Then { error: Error @@ -12,10 +12,12 @@ interface Then { type GivenWhenThen = () => Promise -describe('DefendantController - Get subpoena pdf', () => { +describe('LimitedAccessSubpoenaController - Get subpoena pdf', () => { const caseId = uuid() + const subpoenaId = uuid() + const subpoena = { id: subpoenaId } as Subpoena const defendantId = uuid() - const defendant = { id: defendantId } as Defendant + const defendant = { id: defendantId, subpoenas: [subpoena] } as Defendant const theCase = { id: caseId } as Case const res = { end: jest.fn() } as unknown as Response const pdf = Buffer.from(uuid()) @@ -23,8 +25,8 @@ describe('DefendantController - Get subpoena pdf', () => { let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { pdfService, defendantController } = - await createTestingDefendantModule() + const { pdfService, limitedAccessSubpoenaController } = + await createTestingSubpoenaModule() mockPdfService = pdfService const getSubpoenaPdfMock = mockPdfService.getSubpoenaPdf as jest.Mock @@ -34,11 +36,13 @@ describe('DefendantController - Get subpoena pdf', () => { const then = {} as Then try { - await defendantController.getSubpoenaPdf( + await limitedAccessSubpoenaController.getSubpoenaPdf( caseId, defendantId, + subpoenaId, theCase, defendant, + subpoena, res, ) } catch (error) { @@ -58,6 +62,7 @@ describe('DefendantController - Get subpoena pdf', () => { expect(mockPdfService.getSubpoenaPdf).toHaveBeenCalledWith( theCase, defendant, + subpoena, undefined, undefined, undefined, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdfRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdfRolesRules.spec.ts similarity index 59% rename from apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdfRolesRules.spec.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdfRolesRules.spec.ts index 87cbfde789f8..125817664eb1 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdfRolesRules.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/getSubpoenaPdfRolesRules.spec.ts @@ -1,14 +1,14 @@ import { defenderRule } from '../../../../guards' -import { LimitedAccessDefendantController } from '../../limitedAccessDefendant.controller' +import { LimitedAccessSubpoenaController } from '../../limitedAccessSubpoena.controller' -describe('LimitedAccessDefendantController - Get custody notice pdf rules', () => { +describe('LimitedAccessSubpoenaController - Get custody notice pdf rules', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let rules: any[] beforeEach(() => { rules = Reflect.getMetadata( 'roles-rules', - LimitedAccessDefendantController.prototype.getSubpoenaPdf, + LimitedAccessSubpoenaController.prototype.getSubpoenaPdf, ) }) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/limitedAccessSubpoenaControllerGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/limitedAccessSubpoenaControllerGuards.spec.ts new file mode 100644 index 000000000000..cf4fb036e396 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/limitedAccessSubpoenaController/limitedAccessSubpoenaControllerGuards.spec.ts @@ -0,0 +1,30 @@ +import { JwtAuthGuard, RolesGuard } from '@island.is/judicial-system/auth' +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard, CaseReadGuard, CaseTypeGuard } from '../../../case' +import { DefendantExistsGuard } from '../../../defendant/guards/defendantExists.guard' +import { SubpoenaExistsOptionalGuard } from '../../guards/subpoenaExists.guard' +import { LimitedAccessSubpoenaController } from '../../limitedAccessSubpoena.controller' + +describe('LimitedAccessSubpoenaController - guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata('__guards__', LimitedAccessSubpoenaController) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(7) + expect(new guards[0]()).toBeInstanceOf(JwtAuthGuard) + expect(new guards[1]()).toBeInstanceOf(RolesGuard) + expect(new guards[2]()).toBeInstanceOf(CaseExistsGuard) + expect(guards[3]).toBeInstanceOf(CaseTypeGuard) + expect(guards[3]).toEqual({ + allowedCaseTypes: indictmentCases, + }) + expect(new guards[4]()).toBeInstanceOf(CaseReadGuard) + expect(new guards[5]()).toBeInstanceOf(DefendantExistsGuard) + expect(new guards[6]()).toBeInstanceOf(SubpoenaExistsOptionalGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdf.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdf.spec.ts similarity index 67% rename from apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdf.spec.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdf.spec.ts index 97ec46dde723..2db39b25f289 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/getSubpoenaPdf.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdf.spec.ts @@ -1,10 +1,11 @@ import { Response } from 'express' import { uuid } from 'uuidv4' -import { createTestingDefendantModule } from '../createTestingDefendantModule' +import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' import { Case, PdfService } from '../../../case' -import { Defendant } from '../../models/defendant.model' +import { Defendant } from '../../../defendant/models/defendant.model' +import { Subpoena } from '../../models/subpoena.model' interface Then { error: Error @@ -12,10 +13,12 @@ interface Then { type GivenWhenThen = () => Promise -describe('LimitedAccessDefendantController - Get subpoena pdf', () => { +describe('SubpoenaController - Get subpoena pdf', () => { const caseId = uuid() + const subpoenaId = uuid() + const subpoena = { id: subpoenaId } as Subpoena const defendantId = uuid() - const defendant = { id: defendantId } as Defendant + const defendant = { id: defendantId, subpoenas: [subpoena] } as Defendant const theCase = { id: caseId } as Case const res = { end: jest.fn() } as unknown as Response const pdf = Buffer.from(uuid()) @@ -23,8 +26,8 @@ describe('LimitedAccessDefendantController - Get subpoena pdf', () => { let givenWhenThen: GivenWhenThen beforeEach(async () => { - const { pdfService, limitedAccessDefendantController } = - await createTestingDefendantModule() + const { pdfService, subpoenaController } = + await createTestingSubpoenaModule() mockPdfService = pdfService const getSubpoenaPdfMock = mockPdfService.getSubpoenaPdf as jest.Mock @@ -34,12 +37,14 @@ describe('LimitedAccessDefendantController - Get subpoena pdf', () => { const then = {} as Then try { - await limitedAccessDefendantController.getSubpoenaPdf( + await subpoenaController.getSubpoenaPdf( caseId, defendantId, + subpoenaId, theCase, defendant, res, + subpoena, ) } catch (error) { then.error = error as Error @@ -58,6 +63,7 @@ describe('LimitedAccessDefendantController - Get subpoena pdf', () => { expect(mockPdfService.getSubpoenaPdf).toHaveBeenCalledWith( theCase, defendant, + subpoena, undefined, undefined, undefined, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfRolesRules.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfRolesRules.spec.ts similarity index 80% rename from apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfRolesRules.spec.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfRolesRules.spec.ts index 286050136fcd..9d114d811d81 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/getSubpoenaPdfRolesRules.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/getSubpoenaPdfRolesRules.spec.ts @@ -6,16 +6,16 @@ import { prosecutorRule, publicProsecutorStaffRule, } from '../../../../guards' -import { DefendantController } from '../../defendant.controller' +import { SubpoenaController } from '../../subpoena.controller' -describe('DefendantController - Get custody notice pdf rules', () => { +describe('SubpoenaController - Get custody notice pdf rules', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let rules: any[] beforeEach(() => { rules = Reflect.getMetadata( 'roles-rules', - DefendantController.prototype.getSubpoenaPdf, + SubpoenaController.prototype.getSubpoenaPdf, ) }) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/limitedAccessDefendantControllerGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/subpoenaControllerGuards.spec.ts similarity index 64% rename from apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/limitedAccessDefendantControllerGuards.spec.ts rename to apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/subpoenaControllerGuards.spec.ts index aee6fdebeb8c..966a64fecfef 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/limitedAccessDefendantController/limitedAccessDefendantControllerGuards.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/subpoenaController/subpoenaControllerGuards.spec.ts @@ -2,19 +2,20 @@ import { JwtAuthGuard, RolesGuard } from '@island.is/judicial-system/auth' import { indictmentCases } from '@island.is/judicial-system/types' import { CaseExistsGuard, CaseReadGuard, CaseTypeGuard } from '../../../case' -import { DefendantExistsGuard } from '../../guards/defendantExists.guard' -import { LimitedAccessDefendantController } from '../../limitedAccessDefendant.controller' +import { DefendantExistsGuard } from '../../../defendant/guards/defendantExists.guard' +import { SubpoenaExistsOptionalGuard } from '../../guards/subpoenaExists.guard' +import { SubpoenaController } from '../../subpoena.controller' -describe('LimitedAccessDefendantController - guards', () => { +describe('SubpoenaController - guards', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let guards: any[] beforeEach(() => { - guards = Reflect.getMetadata('__guards__', LimitedAccessDefendantController) + guards = Reflect.getMetadata('__guards__', SubpoenaController) }) it('should have the right guard configuration', () => { - expect(guards).toHaveLength(6) + expect(guards).toHaveLength(7) expect(new guards[0]()).toBeInstanceOf(JwtAuthGuard) expect(new guards[1]()).toBeInstanceOf(RolesGuard) expect(new guards[2]()).toBeInstanceOf(CaseExistsGuard) @@ -24,5 +25,6 @@ describe('LimitedAccessDefendantController - guards', () => { }) expect(new guards[4]()).toBeInstanceOf(CaseReadGuard) expect(new guards[5]()).toBeInstanceOf(DefendantExistsGuard) + expect(new guards[6]()).toBeInstanceOf(SubpoenaExistsOptionalGuard) }) }) diff --git a/apps/judicial-system/message-handler/src/app/messageHandler.service.ts b/apps/judicial-system/message-handler/src/app/messageHandler.service.ts index 98edbe4c608d..56f7488dd2d5 100644 --- a/apps/judicial-system/message-handler/src/app/messageHandler.service.ts +++ b/apps/judicial-system/message-handler/src/app/messageHandler.service.ts @@ -33,7 +33,13 @@ export class MessageHandlerService implements OnModuleDestroy { `${this.config.backendUrl}/api/internal${ message.caseId ? `/case/${message.caseId}` : '' }/${messageEndpoint[message.type]}${ - message.elementId ? `/${message.elementId}` : '' + message.elementId + ? `/${ + Array.isArray(message.elementId) + ? message.elementId.join('/') + : message.elementId + }` + : '' }`, message.user, message.body, diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index f6eeefc5ba75..64acb88c88c8 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -26,6 +26,10 @@ query Case($input: CaseQueryInput!) { verdictViewDate verdictAppealDeadline subpoenaType + subpoenas { + id + created + } } defenderName defenderNationalId diff --git a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql index 774715ba84f3..a5ba9f00d180 100644 --- a/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/limitedAccessCase.graphql @@ -35,6 +35,10 @@ query LimitedAccessCase($input: CaseQueryInput!) { defenderPhoneNumber defenderChoice verdictViewDate + subpoenas { + id + created + } } defenderName defenderNationalId diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts index 8372e2fbbc45..a6e7ee8bee11 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.strings.ts @@ -40,8 +40,8 @@ export const strings = defineMessages({ description: 'Notaður sem titill á dómskjalaskjá í ákærum.', }, subpoenaButtonText: { - id: 'judicial.system.indictments:indictment_case_files_list.subpoena_button_text', - defaultMessage: 'Fyrirkall {name}.pdf', + id: 'judicial.system.indictments:indictment_case_files_list.subpoena_button_text_v2', + defaultMessage: 'Fyrirkall {name} {date}.pdf', description: 'Notaður sem texti á PDF takka til að sækja firyrkall í ákærum.', }, diff --git a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx index 99d714df2a3e..29f1738db99f 100644 --- a/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx +++ b/apps/judicial-system/web/src/components/IndictmentCaseFilesList/IndictmentCaseFilesList.tsx @@ -3,6 +3,7 @@ import { useIntl } from 'react-intl' import { AnimatePresence } from 'framer-motion' import { Box, Text } from '@island.is/island-ui/core' +import { formatDate } from '@island.is/judicial-system/formatters' import { isCompletedCase, isDefenceUser, @@ -73,7 +74,9 @@ const IndictmentCaseFilesList: FC = ({ }) const showTrafficViolationCaseFiles = isTrafficViolationCase(workingCase) - const showSubpoenaPdf = workingCase.arraignmentDate + const showSubpoenaPdf = workingCase.defendants?.some( + (defendant) => defendant.subpoenas && defendant.subpoenas.length > 0, + ) const cf = workingCase.caseFiles @@ -227,28 +230,29 @@ const IndictmentCaseFilesList: FC = ({ )} - {showSubpoenaPdf && - workingCase.defendants && - workingCase.defendants.length > 0 && ( - - - {formatMessage(strings.subpoenaTitle)} - - {workingCase.defendants.map((defendant) => ( - + {showSubpoenaPdf && ( + + + {formatMessage(strings.subpoenaTitle)} + + {workingCase.defendants?.map((defendant) => + defendant.subpoenas?.map((subpoena) => ( + - ))} - - )} + )), + )} + + )} {fileNotFound && } diff --git a/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx b/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx index 8ff7a5bd0fb0..987d1fd4a624 100644 --- a/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx +++ b/apps/judicial-system/web/src/components/PdfButton/PdfButton.tsx @@ -22,7 +22,7 @@ interface Props { disabled?: boolean renderAs?: 'button' | 'row' handleClick?: () => void - elementId?: string + elementId?: string | string[] queryParameters?: string } @@ -46,7 +46,9 @@ const PdfButton: FC> = ({ const prefix = `${limitedAccess ? 'limitedAccess/' : ''}${ connectedCaseParentId ? `mergedCase/${caseId}/` : '' }` - const postfix = elementId ? `/${elementId}` : '' + const postfix = elementId + ? `/${Array.isArray(elementId) ? elementId.join('/') : elementId}` + : '' const query = queryParameters ? `?${queryParameters}` : '' const url = `${api.apiUrl}/api/case/${ connectedCaseParentId ?? caseId diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx index 0df16e8b0a6d..176c08156d9e 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Overview/Overview.tsx @@ -38,6 +38,7 @@ const IndictmentOverview = () => { const [modalVisible, setModalVisible] = useState<'RETURN_INDICTMENT'>() const latestDate = workingCase.courtDate ?? workingCase.arraignmentDate + const isArraignmentScheduled = Boolean(workingCase.arraignmentDate) // const caseHasBeenReceivedByCourt = workingCase.state === CaseState.RECEIVED const handleNavigationTo = useCallback( @@ -122,7 +123,10 @@ const IndictmentOverview = () => { { ({ + defendant, + disabled: isArraignmentScheduled, + }))} workingCase={workingCase} setWorkingCase={setWorkingCase} updateDefendantState={updateDefendantState} diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.strings.ts b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.strings.ts index 8c8c4c2c47e3..baa01967b083 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.strings.ts +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.strings.ts @@ -60,4 +60,10 @@ export const subpoena = defineMessages({ description: 'Notaður sem texti fyrir Handtökufyrirkall valkost á Fyrirkalls skjá í dómaraflæði í ákærum.', }, + newSubpoenaButtonText: { + id: 'judicial.system.core:subpoena.new_subpoena_button_text', + defaultMessage: 'Nýtt fyrirkall', + description: + 'Notaður sem texti á takka sem býður notanda að búa til nýtt fyrirkall.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx index 11c767d7008f..0eab4a0af95b 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx @@ -2,8 +2,9 @@ import { FC, useCallback, useContext, useState } from 'react' import { useIntl } from 'react-intl' import router from 'next/router' -import { Box } from '@island.is/island-ui/core' +import { Box, Button } from '@island.is/island-ui/core' import * as constants from '@island.is/judicial-system/consts' +import { formatDate } from '@island.is/judicial-system/formatters' import { titles } from '@island.is/judicial-system-web/messages' import { CourtArrangements, @@ -30,6 +31,7 @@ const Subpoena: FC = () => { const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) const [navigateTo, setNavigateTo] = useState() + const [newSubpoenas, setNewSubpoenas] = useState([]) const { updateDefendantState, updateDefendant } = useDefendants() const { formatMessage } = useIntl() const { @@ -40,15 +42,21 @@ const Subpoena: FC = () => { } = useCourtArrangements(workingCase, setWorkingCase, 'arraignmentDate') const isArraignmentScheduled = Boolean(workingCase.arraignmentDate) + const isSchedulingArraignmentDate = + !isArraignmentScheduled || newSubpoenas.length > 0 + + const isSchedulingArraignmentDateForDefendant = (defendantId: string) => + !isArraignmentScheduled || + (isArraignmentScheduled && newSubpoenas.includes(defendantId)) const handleNavigationTo = useCallback( async (destination: keyof stepValidationsType) => { - if (isArraignmentScheduled) { + if (!isSchedulingArraignmentDate) { router.push(`${destination}/${workingCase.id}`) return } - const promises: Promise[] = [sendCourtDateToServer()] + const promises: Promise[] = [] if (workingCase.defendants) { workingCase.defendants.forEach((defendant) => { @@ -62,15 +70,23 @@ const Subpoena: FC = () => { }) } - const allDataSentToServer = await Promise.all(promises) - if (!allDataSentToServer.every((result) => result)) { + // Make sure defendants are updated before submitting the court date + const allDefendantsUpdated = await Promise.all(promises) + + if (!allDefendantsUpdated.every((result) => result)) { + return + } + + const courtDateUpdated = await sendCourtDateToServer() + + if (!courtDateUpdated) { return } router.push(`${destination}/${workingCase.id}`) }, [ - isArraignmentScheduled, + isSchedulingArraignmentDate, sendCourtDateToServer, workingCase.defendants, workingCase.id, @@ -96,7 +112,27 @@ const Subpoena: FC = () => { { ({ + defendant, + disabled: + isArraignmentScheduled && + !newSubpoenas.includes(defendant.id), + children: isArraignmentScheduled && ( + + ), + }))} workingCase={workingCase} setWorkingCase={setWorkingCase} updateDefendantState={updateDefendantState} @@ -112,32 +148,59 @@ const Subpoena: FC = () => { handleCourtDateChange={handleCourtDateChange} handleCourtRoomChange={handleCourtRoomChange} courtDate={workingCase.arraignmentDate} - dateTimeDisabled={isArraignmentScheduled} - courtRoomDisabled={isArraignmentScheduled} + dateTimeDisabled={!isSchedulingArraignmentDate} + courtRoomDisabled={!isSchedulingArraignmentDate} courtRoomRequired /> - {workingCase.defendants?.map((defendant, index) => ( - - - + {workingCase.defendants?.map((defendant, dIndex) => ( + <> + {isSchedulingArraignmentDateForDefendant(defendant.id) && ( + + + + )} + {defendant.subpoenas?.map((subpoena, sIndex) => ( + + + + ))} + ))} @@ -147,14 +210,14 @@ const Subpoena: FC = () => { previousUrl={`${constants.INDICTMENTS_RECEPTION_AND_ASSIGNMENT_ROUTE}/${workingCase.id}`} nextIsLoading={isLoadingWorkingCase} onNextButtonClick={() => { - if (isArraignmentScheduled) { + if (!isSchedulingArraignmentDate) { router.push( `${constants.INDICTMENTS_DEFENDER_ROUTE}/${workingCase.id}`, ) } else setNavigateTo(constants.INDICTMENTS_DEFENDER_ROUTE) }} nextButtonText={ - isArraignmentScheduled + !isSchedulingArraignmentDate ? undefined : formatMessage(strings.nextButtonText) } diff --git a/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx b/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx index e30edd9bd129..4b36142baa59 100644 --- a/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx +++ b/apps/judicial-system/web/src/routes/Court/components/SubpoenaType/SubpoenaType.tsx @@ -1,4 +1,4 @@ -import { Dispatch, FC, SetStateAction } from 'react' +import { Dispatch, FC, ReactNode, SetStateAction } from 'react' import { useIntl } from 'react-intl' import { Box, RadioButton, Text } from '@island.is/island-ui/core' @@ -17,7 +17,11 @@ import { strings } from './SubpoenaType.strings' import * as styles from '../../Indictments/Subpoena/Subpoena.css' interface SubpoenaTypeProps { - defendants: Defendant[] + subpoenaItems: { + defendant: Defendant + disabled?: boolean + children?: ReactNode + }[] workingCase: Case setWorkingCase: Dispatch> updateDefendantState: ( @@ -28,70 +32,79 @@ interface SubpoenaTypeProps { } const SubpoenaType: FC = ({ - defendants, + subpoenaItems, workingCase, setWorkingCase, updateDefendantState, required = true, }) => { const { formatMessage } = useIntl() - const isArraignmentDone = Boolean(workingCase.indictmentDecision) + return ( <> - {defendants.map((defendant, index) => ( + {subpoenaItems.map((item, index) => ( - - - {defendant.name} - - - { - updateDefendantState( - { - caseId: workingCase.id, - defendantId: defendant.id, - subpoenaType: SubpoenaTypeEnum.ABSENCE, - }, - setWorkingCase, - ) - }} - disabled={isArraignmentDone} - /> - { - updateDefendantState( - { - caseId: workingCase.id, - defendantId: defendant.id, - subpoenaType: SubpoenaTypeEnum.ARREST, - }, - setWorkingCase, - ) - }} - disabled={isArraignmentDone} - /> - - + + + + {item.defendant.name} + + + { + updateDefendantState( + { + caseId: workingCase.id, + defendantId: item.defendant.id, + subpoenaType: SubpoenaTypeEnum.ABSENCE, + }, + setWorkingCase, + ) + }} + disabled={item.disabled} + /> + { + updateDefendantState( + { + caseId: workingCase.id, + defendantId: item.defendant.id, + subpoenaType: SubpoenaTypeEnum.ARREST, + }, + setWorkingCase, + ) + }} + disabled={item.disabled} + /> + + + + {item.children} ))} diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index 99bfc42f74b0..1b5aaf576864 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -66,7 +66,7 @@ export type Message = { type: MessageType user?: User caseId?: string - elementId?: string + elementId?: string | string[] body?: { [key: string]: unknown } numberOfRetries?: number nextRetry?: number