diff --git a/apps/judicial-system/backend/src/app/formatters/serviceCertificatePdf.ts b/apps/judicial-system/backend/src/app/formatters/serviceCertificatePdf.ts index 1e4e7a45a8ea..43f9eb9eda27 100644 --- a/apps/judicial-system/backend/src/app/formatters/serviceCertificatePdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/serviceCertificatePdf.ts @@ -6,10 +6,12 @@ import { capitalize, formatDate, formatDOB, + formatNationalId, getWordByGender, Word, } from '@island.is/judicial-system/formatters' import { + DefenderChoice, ServiceStatus, SubpoenaType, UserRole, @@ -21,8 +23,7 @@ import { Defendant } from '../modules/defendant' import { Subpoena } from '../modules/subpoena' import { addEmptyLines, - addFooter, - addHugeHeading, + addLargeHeading, addMediumCenteredText, addNormalCenteredText, addNormalText, @@ -61,7 +62,7 @@ export const createServiceCertificate = ( const doc = new PDFDocument({ size: 'A4', margins: { - top: 40, + top: 80, bottom: 60, left: 50, right: 50, @@ -75,7 +76,7 @@ export const createServiceCertificate = ( setTitle(doc, formatMessage(strings.title)) - addHugeHeading(doc, formatMessage(strings.title).toUpperCase(), 'Times-Bold') + addLargeHeading(doc, formatMessage(strings.title).toUpperCase(), 'Times-Bold') addMediumCenteredText( doc, `Mál nr. ${theCase.courtCaseNumber || ''}`, @@ -83,17 +84,17 @@ export const createServiceCertificate = ( ) addNormalCenteredText(doc, theCase.court?.name || '', 'Times-Bold') - addEmptyLines(doc, 2) + addEmptyLines(doc, 3) addMediumCenteredText( doc, `Birting tókst ${ - subpoena.serviceDate ? formatDate(subpoena.serviceDate, 'PPp') : '' + subpoena.serviceDate ? formatDate(subpoena.serviceDate, 'PPPp') : '' }`, 'Times-Bold', ) - addEmptyLines(doc) + addEmptyLines(doc, 2) addNormalText(doc, 'Birtingaraðili: ', 'Times-Bold', true) addNormalText( @@ -104,6 +105,8 @@ export const createServiceCertificate = ( 'Times-Roman', ) + addEmptyLines(doc) + if (subpoena.serviceStatus !== ServiceStatus.ELECTRONICALLY) { addNormalText(doc, 'Athugasemd: ', 'Times-Bold', true) addNormalText( @@ -117,26 +120,34 @@ export const createServiceCertificate = ( ) } - addEmptyLines(doc, 2) + addEmptyLines(doc, 3) - addNormalText( - doc, - `${capitalize(getWordByGender(Word.AKAERDI, defendant.gender))}: `, - 'Times-Bold', - true, + const defendantText = capitalize( + getWordByGender(Word.AKAERDI, defendant.gender), ) + const defendantLabel = `${defendantText}: ` + + addNormalText(doc, defendantLabel, 'Times-Bold', true) addNormalText( doc, - defendant.name && defendant.nationalId && defendant.address + defendant.name && defendant.nationalId ? `${defendant.name}, ${formatDOB( defendant.nationalId, defendant.noNationalId, - )}, ${defendant.address}` + )}` : 'Ekki skráður', 'Times-Roman', ) - addEmptyLines(doc, 2) + // Consider adding indentation to helper functions + doc.text( + ` ${defendant.address ?? 'Ekki skráð'}`, + 50 + doc.widthOfString(defendantLabel), + doc.y + 5, + ) + doc.text('', 50) + + addEmptyLines(doc, 3) addNormalText(doc, 'Ákærandi: ', 'Times-Bold', true) addNormalText( @@ -147,6 +158,8 @@ export const createServiceCertificate = ( 'Times-Roman', ) + addEmptyLines(doc) + addNormalText(doc, `${getRole(theCase.judge?.role)}: `, 'Times-Bold', true) addNormalText( doc, @@ -154,7 +167,7 @@ export const createServiceCertificate = ( 'Times-Roman', ) - addEmptyLines(doc) + addEmptyLines(doc, 3) addNormalText(doc, 'Þingfesting: ', 'Times-Bold', true) addNormalText( @@ -166,13 +179,59 @@ export const createServiceCertificate = ( 'Times-Roman', ) + addEmptyLines(doc) + addNormalText(doc, 'Staður: ', 'Times-Bold', true) - addNormalText(doc, subpoena.location || 'Ekki skráður', 'Times-Roman') + addNormalText( + doc, + `Dómsalur ${subpoena.location}` || 'ekki skráður', + 'Times-Roman', + ) + + addEmptyLines(doc) addNormalText(doc, 'Tegund fyrirkalls: ', 'Times-Bold', true) addNormalText(doc, getSubpoenaType(defendant.subpoenaType), 'Times-Roman') - addFooter(doc) + addEmptyLines(doc, 3) + + let defenderChoiceText = '' + + switch (defendant.requestedDefenderChoice) { + case DefenderChoice.CHOOSE: + defenderChoiceText = + 'Ég óska þess að valinn lögmaður verði skipaður verjandi minn.' + break + case DefenderChoice.WAIVE: + defenderChoiceText = 'Ég óska ekki eftir verjanda.' + break + case DefenderChoice.DELEGATE: + defenderChoiceText = + 'Ég fel dómara málsins að tilnefna og skipa mér verjanda.' + break + case DefenderChoice.DELAY: + default: + defenderChoiceText = + 'Ég óska eftir fresti fram að þingfestingu til þess að tilnefna verjanda.' + } + + addNormalText(doc, 'Afstaða til verjanda: ', 'Times-Bold', true) + addNormalText(doc, defenderChoiceText, 'Times-Roman') + + if (defendant.requestedDefenderChoice === DefenderChoice.CHOOSE) { + addEmptyLines(doc) + + addNormalText( + doc, + defendant.requestedDefenderName && defendant.requestedDefenderNationalId + ? `${defendant.requestedDefenderName}, kt. ${formatNationalId( + defendant.requestedDefenderNationalId, + )}` + : 'Ekki skráður', + 'Times-Roman', + ) + } + doc.end() return new Promise((resolve) => 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 f8ed15c90f31..6da59567610a 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 @@ -49,6 +49,7 @@ import { isInvestigationCase, isRequestCase, notificationTypes, + ServiceStatus, StringType, stringTypes, UserRole, @@ -1359,7 +1360,7 @@ export class CaseService { (updatedDefendant) => theCase.defendants?.find( (defendant) => defendant.id === updatedDefendant.id, - )?.subpoenas?.[0]?.id !== updatedDefendant.subpoenas?.[0]?.id, + )?.subpoenas?.[0]?.id !== updatedDefendant.subpoenas?.[0]?.id, // Only deliver new subpoenas ) .map((updatedDefendant) => [ ...(updatedCase.origin === CaseOrigin.LOKE @@ -1400,6 +1401,37 @@ export class CaseService { } } + private addMessagesForIndictmentArraignmentCompletionToQueue( + theCase: Case, + user: TUser, + ): Promise { + const messages: Message[] = [] + + theCase.defendants?.forEach((defendant) => { + const subpoena = defendant.subpoenas?.[0] + + const hasSubpoenaBeenSuccessfullyServedToDefendant = + subpoena?.serviceStatus && + [ + ServiceStatus.DEFENDER, + ServiceStatus.ELECTRONICALLY, + ServiceStatus.IN_PERSON, + ].includes(subpoena.serviceStatus) + + // Only send certificates for subpoenas which have been successfully served + if (hasSubpoenaBeenSuccessfullyServedToDefendant) { + messages.push({ + type: MessageType.DELIVERY_TO_COURT_SERVICE_CERTIFICATE, + user, + caseId: theCase.id, + elementId: [defendant.id, subpoena.id], + }) + } + }) + + return this.messageService.sendMessagesToQueue(messages) + } + private async addMessagesForUpdatedCaseToQueue( theCase: Case, updatedCase: Case, @@ -1610,6 +1642,14 @@ export class CaseService { await this.addMessagesForNewSubpoenasToQueue(theCase, updatedCase, user) } + + // This only applies to indictments and only when an arraignment has been completed + if (updatedCase.indictmentDecision && !theCase.indictmentDecision) { + await this.addMessagesForIndictmentArraignmentCompletionToQueue( + updatedCase, + user, + ) + } } private allAppealRolesAssigned(updatedCase: Case) { 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 5d1879059ae6..ffb7729a8f31 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 @@ -162,6 +162,42 @@ export class InternalSubpoenaController { ) } + @UseGuards( + CaseExistsGuard, + new CaseTypeGuard(indictmentCases), + DefendantExistsGuard, + SubpoenaExistsGuard, + ) + @Post( + `case/:caseId/${ + messageEndpoint[MessageType.DELIVERY_TO_COURT_SERVICE_CERTIFICATE] + }/:defendantId/:subpoenaId`, + ) + @ApiOkResponse({ + type: DeliverResponse, + description: 'Delivers a service certificate to the court', + }) + deliverServiceCertificateToCourt( + @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 service certificate pdf to court for subpoena ${subpoenaId} of defendant ${defendantId} and case ${caseId}`, + ) + + return this.subpoenaService.deliverServiceCertificateToCourt( + theCase, + defendant, + subpoena, + deliverDto.user, + ) + } + @UseGuards( CaseExistsGuard, new CaseTypeGuard(indictmentCases), 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 3b1e0f9b2697..74356de9d09d 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 @@ -432,6 +432,41 @@ export class SubpoenaService { }) } + async deliverServiceCertificateToCourt( + theCase: Case, + defendant: Defendant, + subpoena: Subpoena, + user: TUser, + ): Promise { + return this.pdfService + .getServiceCertificatePdf(theCase, defendant, subpoena) + .then(async (pdf) => { + const fileName = `Birtingarvottorð - ${defendant.name}` + + return this.courtService.createDocument( + user, + theCase.id, + theCase.courtId, + theCase.courtCaseNumber, + CourtDocumentFolder.SUBPOENA_DOCUMENTS, + fileName, + `${fileName}.pdf`, + 'application/pdf', + pdf, + ) + }) + .then(() => ({ delivered: true })) + .catch((reason) => { + // Tolerate failure, but log error + this.logger.warn( + `Failed to upload service certificate pdf to court for subpoena ${subpoena.id} of defendant ${defendant.id} and case ${theCase.id}`, + { reason }, + ) + + return { delivered: false } + }) + } + async deliverSubpoenaRevocationToPolice( theCase: Case, subpoena: Subpoena, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourt.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourt.spec.ts new file mode 100644 index 000000000000..de63e819b4d2 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourt.spec.ts @@ -0,0 +1,113 @@ +import { uuid } from 'uuidv4' + +import { createTestingSubpoenaModule } from '../createTestingSubpoenaModule' + +import { Case, PdfService } from '../../../case' +import { CourtService } from '../../../court' +import { Defendant } from '../../../defendant' +import { DeliverDto } from '../../dto/deliver.dto' +import { DeliverResponse } from '../../models/deliver.response' +import { Subpoena } from '../../models/subpoena.model' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = () => Promise + +describe('InternalSubpoenaController - Deliver subpoena certificate to court', () => { + const caseId = uuid() + const courtId = uuid() + const courtCaseNumber = uuid() + const subpoenaId = uuid() + const defendantId = uuid() + const defendantName = uuid() + + const subpoena = { id: subpoenaId } as Subpoena + const defendant = { + id: defendantId, + name: defendantName, + subpoenas: [subpoena], + } as Defendant + const theCase = { + id: caseId, + courtId, + courtCaseNumber, + defendants: [defendant], + } as Case + const user = { id: uuid() } + const dto = { user } as DeliverDto + + let mockPdfService: PdfService + let mockCourtService: CourtService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { courtService, pdfService, internalSubpoenaController } = + await createTestingSubpoenaModule() + + mockPdfService = pdfService + const mockGetServiceCertificatePdf = + mockPdfService.getServiceCertificatePdf as jest.Mock + mockGetServiceCertificatePdf.mockRejectedValue(new Error('Some error')) + + mockCourtService = courtService + const mockCreateDocument = mockCourtService.createDocument as jest.Mock + mockCreateDocument.mockRejectedValue(new Error('Some error')) + + givenWhenThen = async () => { + const then = {} as Then + + await internalSubpoenaController + .deliverServiceCertificateToCourt( + caseId, + defendantId, + subpoenaId, + theCase, + defendant, + subpoena, + dto, + ) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('service certificate delivered to court', () => { + const serviceCertificatePdf = uuid() + let then: Then + + beforeEach(async () => { + const mockGetServiceCertificatePdf = + mockPdfService.getServiceCertificatePdf as jest.Mock + mockGetServiceCertificatePdf.mockResolvedValue(serviceCertificatePdf) + const mockCreateDocument = mockCourtService.createDocument as jest.Mock + mockCreateDocument.mockResolvedValue('') + + then = await givenWhenThen() + }) + + it('should deliver the service certificate', () => { + expect(mockPdfService.getServiceCertificatePdf).toBeCalledWith( + theCase, + defendant, + subpoena, + ) + expect(mockCourtService.createDocument).toBeCalledWith( + user, + caseId, + courtId, + courtCaseNumber, + 'Boðanir', + `Birtingarvottorð - ${defendantName}`, + `Birtingarvottorð - ${defendantName}.pdf`, + 'application/pdf', + serviceCertificatePdf, + ) + expect(then.result).toEqual({ delivered: true }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourtGuards.spec.ts new file mode 100644 index 000000000000..7e1b0232f4cc --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/internalSubpoenaController/deliverServiceCertificateToCourtGuards.spec.ts @@ -0,0 +1,29 @@ +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseExistsGuard, CaseTypeGuard } from '../../../case' +import { DefendantExistsGuard } from '../../../defendant' +import { SubpoenaExistsGuard } from '../../guards/subpoenaExists.guard' +import { InternalSubpoenaController } from '../../internalSubpoena.controller' + +describe('InternalSubpoenaController - Deliver service certificate to court guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalSubpoenaController.prototype.deliverServiceCertificateToCourt, + ) + }) + + 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(DefendantExistsGuard) + expect(new guards[3]()).toBeInstanceOf(SubpoenaExistsGuard) + }) +}) diff --git a/libs/judicial-system/message/src/lib/message.ts b/libs/judicial-system/message/src/lib/message.ts index 340906fef8dc..f40d4e5fb28c 100644 --- a/libs/judicial-system/message/src/lib/message.ts +++ b/libs/judicial-system/message/src/lib/message.ts @@ -12,6 +12,7 @@ export enum MessageType { DELIVERY_TO_COURT_CASE_FILES_RECORD = 'DELIVERY_TO_COURT_CASE_FILES_RECORD', DELIVERY_TO_COURT_REQUEST = 'DELIVERY_TO_COURT_REQUEST', DELIVERY_TO_COURT_SUBPOENA = 'DELIVERY_TO_COURT_SUBPOENA', + DELIVERY_TO_COURT_SERVICE_CERTIFICATE = 'DELIVERY_TO_COURT_SERVICE_CERTIFICATE', DELIVERY_TO_COURT_COURT_RECORD = 'DELIVERY_TO_COURT_COURT_RECORD', DELIVERY_TO_COURT_SIGNED_RULING = 'DELIVERY_TO_COURT_SIGNED_RULING', DELIVERY_TO_COURT_CASE_CONCLUSION = 'DELIVERY_TO_COURT_CASE_CONCLUSION', @@ -54,6 +55,7 @@ export const messageEndpoint: { [key in MessageType]: string } = { DELIVERY_TO_COURT_CASE_FILES_RECORD: 'deliverCaseFilesRecordToCourt', DELIVERY_TO_COURT_REQUEST: 'deliverRequestToCourt', DELIVERY_TO_COURT_SUBPOENA: 'deliverSubpoenaToCourt', + DELIVERY_TO_COURT_SERVICE_CERTIFICATE: 'deliverServiceCertificateToCourt', DELIVERY_TO_COURT_COURT_RECORD: 'deliverCourtRecordToCourt', DELIVERY_TO_COURT_SIGNED_RULING: 'deliverSignedRulingToCourt', DELIVERY_TO_COURT_CASE_CONCLUSION: 'deliverCaseConclusionToCourt',