diff --git a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts index 95975ec85792..0dc40cde6cef 100644 --- a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts +++ b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts @@ -6,12 +6,7 @@ import { Inject, Injectable } from '@nestjs/common' import { type ConfigType } from '@island.is/nest/config' import { ProblemError } from '@island.is/nest/problem' -import { - CommentType, - DateType, - type User, - UserRole, -} from '@island.is/judicial-system/types' +import { DateType, type User, UserRole } from '@island.is/judicial-system/types' import { Case, @@ -144,7 +139,6 @@ export class BackendService extends DataSource<{ req: Request }> { private caseTransformer(data: unknown): Case { const theCase = data as Case & { dateLogs?: { dateType: DateType; date: string }[] - explanatoryComments?: { commentType: CommentType; comment: string }[] } return { @@ -155,11 +149,6 @@ export class BackendService extends DataSource<{ req: Request }> { courtDate: theCase.dateLogs?.find( (dateLog) => dateLog.dateType === DateType.COURT_DATE, ), - postponedIndefinitelyExplanation: theCase.explanatoryComments?.find( - (comment) => - comment.commentType === - CommentType.POSTPONED_INDEFINITELY_EXPLANATION, - )?.comment, } } diff --git a/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts b/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts index 65059587bf76..0e506a67f685 100644 --- a/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts +++ b/apps/judicial-system/api/src/app/modules/case/dto/updateCase.input.ts @@ -506,4 +506,9 @@ export class UpdateCaseInput { @IsOptional() @Field(() => ID, { nullable: true }) readonly mergeCaseId?: string + + @Allow() + @IsOptional() + @Field(() => String, { nullable: true }) + readonly civilDemands?: string } diff --git a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts index 247a8cc98056..0f09dd9b6274 100644 --- a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts @@ -453,4 +453,7 @@ export class Case { @Field(() => [Case], { nullable: true }) readonly mergedCases?: Case[] + + @Field(() => String, { nullable: true }) + readonly civilDemands?: string } diff --git a/apps/judicial-system/backend/migrations/20240909194458-rename-explanatory-comment.js b/apps/judicial-system/backend/migrations/20240909194458-rename-explanatory-comment.js new file mode 100644 index 000000000000..8235e54c67c7 --- /dev/null +++ b/apps/judicial-system/backend/migrations/20240909194458-rename-explanatory-comment.js @@ -0,0 +1,50 @@ +'use strict' + +module.exports = { + async up(queryInterface) { + return queryInterface.sequelize.transaction(async (transaction) => + queryInterface + .renameTable('explanatory_comment', 'case_string', { + transaction, + }) + .then(() => + Promise.all([ + queryInterface.renameColumn( + 'case_string', + 'comment_type', + 'string_type', + { transaction }, + ), + queryInterface.renameColumn('case_string', 'comment', 'value', { + transaction, + }), + ]), + ), + ) + }, + + async down(queryInterface) { + return queryInterface.sequelize.transaction(async (transaction) => + queryInterface + .renameTable('case_string', 'explanatory_comment', { + transaction, + }) + .then(() => + Promise.all([ + queryInterface.renameColumn( + 'explanatory_comment', + 'string_type', + 'comment_type', + { transaction }, + ), + queryInterface.renameColumn( + 'explanatory_comment', + 'value', + 'comment', + { transaction }, + ), + ]), + ), + ) + }, +} diff --git a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts index 5ab70b81c81a..0279dbb208f9 100644 --- a/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts +++ b/apps/judicial-system/backend/src/app/formatters/indictmentPdf.ts @@ -8,6 +8,7 @@ import { formatDate, lowercase } from '@island.is/judicial-system/formatters' import { nowFactory } from '../factories' import { indictment } from '../messages' import { Case } from '../modules/case' +import { CaseString } from '../modules/case/models/caseString.model' import { addEmptyLines, addGiganticHeading, @@ -69,10 +70,7 @@ export const createIndictment = async ( doc.on('data', (chunk) => sinc.push(chunk)) - const title = formatMessage(indictment.title) - const heading = formatMessage(indictment.heading) - - setTitle(doc, title) + setTitle(doc, formatMessage(indictment.title)) if (confirmation) { addIndictmentConfirmation(doc, confirmation) @@ -80,7 +78,7 @@ export const createIndictment = async ( addEmptyLines(doc, 6, doc.page.margins.left) - addGiganticHeading(doc, heading, 'Times-Roman') + addGiganticHeading(doc, formatMessage(indictment.heading), 'Times-Roman') addNormalPlusText(doc, ' ') setLineCap(2) addNormalPlusText(doc, theCase.indictmentIntroduction ?? '') @@ -103,6 +101,15 @@ export const createIndictment = async ( addEmptyLines(doc, 2) addNormalPlusJustifiedText(doc, theCase.demands ?? '') + + const civilDemands = CaseString.civilDemands(theCase.caseStrings) + + if (civilDemands) { + addEmptyLines(doc, 2) + addNormalPlusText(doc, formatMessage(indictment.civilDemandsHeading)) + addNormalPlusJustifiedText(doc, civilDemands) + } + addEmptyLines(doc, 2) addNormalPlusCenteredText( doc, diff --git a/apps/judicial-system/backend/src/app/messages/pdfIndictment.ts b/apps/judicial-system/backend/src/app/messages/pdfIndictment.ts index aa824eff2fc6..e74344133512 100644 --- a/apps/judicial-system/backend/src/app/messages/pdfIndictment.ts +++ b/apps/judicial-system/backend/src/app/messages/pdfIndictment.ts @@ -17,4 +17,9 @@ export const indictment = defineMessages({ defaultMessage: 'ÁKÆRA', description: 'Notaður sem heading á ákæru PDF', }, + civilDemandsHeading: { + id: 'judicial.system.backend:pdf.indictment.civil_demands_heading', + defaultMessage: 'Einkaréttarkrafa:', + description: 'Notaður sem titill á einkaréttarkröfu í ákæru PDF', + }, }) diff --git a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts index 7b0da96f91d1..9efb4338c21f 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.controller.ts @@ -99,6 +99,10 @@ import { prosecutorUpdateRule, publicProsecutorStaffUpdateRule, } from './guards/rolesRules' +import { + CaseInterceptor, + CasesInterceptor, +} from './interceptors/case.interceptor' import { CaseListInterceptor } from './interceptors/caseList.interceptor' import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor' import { Case } from './models/case.model' @@ -140,6 +144,7 @@ export class CaseController { @UseGuards(JwtAuthGuard, RolesGuard) @RolesRules(prosecutorRule, prosecutorRepresentativeRule) + @UseInterceptors(CaseInterceptor) @Post('case') @ApiCreatedResponse({ type: Case, description: 'Creates a new case' }) async create( @@ -167,6 +172,7 @@ export class CaseController { courtOfAppealsAssistantUpdateRule, publicProsecutorStaffUpdateRule, ) + @UseInterceptors(CaseInterceptor) @Patch('case/:caseId') @ApiOkResponse({ type: Case, description: 'Updates an existing case' }) async update( @@ -284,6 +290,7 @@ export class CaseController { courtOfAppealsRegistrarTransitionRule, courtOfAppealsAssistantTransitionRule, ) + @UseInterceptors(CaseInterceptor) @Patch('case/:caseId/state') @ApiOkResponse({ type: Case, @@ -438,13 +445,13 @@ export class CaseController { prisonSystemStaffRule, defenderRule, ) + @UseInterceptors(CaseListInterceptor) @Get('cases') @ApiOkResponse({ type: Case, isArray: true, description: 'Gets all existing cases', }) - @UseInterceptors(CaseListInterceptor) getAll(@CurrentHttpUser() user: User): Promise { this.logger.debug('Getting all cases') @@ -463,9 +470,9 @@ export class CaseController { courtOfAppealsRegistrarRule, courtOfAppealsAssistantRule, ) + @UseInterceptors(CompletedAppealAccessedInterceptor, CaseInterceptor) @Get('case/:caseId') @ApiOkResponse({ type: Case, description: 'Gets an existing case' }) - @UseInterceptors(CompletedAppealAccessedInterceptor) getById(@Param('caseId') caseId: string, @CurrentCase() theCase: Case): Case { this.logger.debug(`Getting case ${caseId} by id`) @@ -478,6 +485,7 @@ export class CaseController { districtCourtRegistrarRule, districtCourtAssistantRule, ) + @UseInterceptors(CasesInterceptor) @Get('case/:caseId/connectedCases') @ApiOkResponse({ type: [Case], description: 'Gets all connected cases' }) async getConnectedCases( @@ -872,6 +880,7 @@ export class CaseController { CaseReadGuard, ) @RolesRules(prosecutorRule) + @UseInterceptors(CaseInterceptor) @Post('case/:caseId/extend') @ApiCreatedResponse({ type: Case, @@ -901,6 +910,7 @@ export class CaseController { districtCourtRegistrarRule, districtCourtAssistantRule, ) + @UseInterceptors(CaseInterceptor) @Post('case/:caseId/court') @ApiCreatedResponse({ type: Case, 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 40cd18358f20..10d099b3068b 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 @@ -19,8 +19,8 @@ import { } from '../index' import { Case } from './models/case.model' import { CaseArchive } from './models/caseArchive.model' +import { CaseString } from './models/caseString.model' import { DateLog } from './models/dateLog.model' -import { ExplanatoryComment } from './models/explanatoryComment.model' import { CaseController } from './case.controller' import { CaseService } from './case.service' import { InternalCaseController } from './internalCase.controller' @@ -43,12 +43,7 @@ import { PdfService } from './pdf.service' forwardRef(() => EventModule), forwardRef(() => PoliceModule), forwardRef(() => EventLogModule), - SequelizeModule.forFeature([ - Case, - CaseArchive, - DateLog, - ExplanatoryComment, - ]), + SequelizeModule.forFeature([Case, CaseArchive, DateLog, CaseString]), ], providers: [ CaseService, 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 f1ac7a7078fc..ddd76c7772d4 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 @@ -37,7 +37,6 @@ import { CaseState, CaseTransition, CaseType, - CommentType, DateType, EventType, isCompletedCase, @@ -45,6 +44,7 @@ import { isRequestCase, isTrafficViolationCase, NotificationType, + StringType, UserRole, } from '@island.is/judicial-system/types' @@ -66,8 +66,8 @@ import { User } from '../user' import { CreateCaseDto } from './dto/createCase.dto' import { getCasesQueryFilter } from './filters/cases.filter' import { Case } from './models/case.model' +import { CaseString } from './models/caseString.model' import { DateLog } from './models/dateLog.model' -import { ExplanatoryComment } from './models/explanatoryComment.model' import { SignatureConfirmationResponse } from './models/signatureConfirmation.response' import { transitionCase } from './state/case.state' import { caseModuleConfig } from './case.config' @@ -178,6 +178,7 @@ export interface UpdateCase indictmentReturnedExplanation?: string | null indictmentDeniedExplanation?: string | null indictmentHash?: string | null + civilDemands?: string | null } type DateLogKeys = keyof Pick @@ -187,19 +188,20 @@ const dateLogTypes: Record = { courtDate: DateType.COURT_DATE, } -type ExplanatoryCommentKeys = keyof Pick< +type CaseStringKeys = keyof Pick< UpdateCase, - 'postponedIndefinitelyExplanation' + 'postponedIndefinitelyExplanation' | 'civilDemands' > -const explanatoryCommentTypes: Record = { +const caseStringTypes: Record = { postponedIndefinitelyExplanation: - CommentType.POSTPONED_INDEFINITELY_EXPLANATION, + StringType.POSTPONED_INDEFINITELY_EXPLANATION, + civilDemands: StringType.CIVIL_DEMANDS, } const eventTypes = Object.values(EventType) const dateTypes = Object.values(DateType) -const commentTypes = Object.values(CommentType) +const stringTypes = Object.values(StringType) export const include: Includeable[] = [ { model: Institution, as: 'prosecutorsOffice' }, @@ -293,10 +295,10 @@ export const include: Includeable[] = [ where: { dateType: { [Op.in]: dateTypes } }, }, { - model: ExplanatoryComment, - as: 'explanatoryComments', + model: CaseString, + as: 'caseStrings', required: false, - where: { commentType: { [Op.in]: commentTypes } }, + where: { stringType: { [Op.in]: stringTypes } }, }, { model: Notification, as: 'notifications' }, { model: Case, as: 'mergeCase' }, @@ -374,10 +376,10 @@ export const caseListInclude: Includeable[] = [ where: { dateType: { [Op.in]: dateTypes } }, }, { - model: ExplanatoryComment, - as: 'explanatoryComments', + model: CaseString, + as: 'caseStrings', required: false, - where: { commentType: { [Op.in]: commentTypes } }, + where: { stringType: { [Op.in]: stringTypes } }, }, { model: EventLog, @@ -400,8 +402,8 @@ export class CaseService { @InjectConnection() private readonly sequelize: Sequelize, @InjectModel(Case) private readonly caseModel: typeof Case, @InjectModel(DateLog) private readonly dateLogModel: typeof DateLog, - @InjectModel(ExplanatoryComment) - private readonly explanatoryCommentModel: typeof ExplanatoryComment, + @InjectModel(CaseString) + private readonly caseStringModel: typeof CaseString, @Inject(caseModuleConfig.KEY) private readonly config: ConfigType, private readonly defendantService: DefendantService, @@ -1468,39 +1470,39 @@ export class CaseService { update: UpdateCase, transaction: Transaction, ) { - // Iterate over all known explanatory comment types - for (const key in explanatoryCommentTypes) { - const commentKey = key as ExplanatoryCommentKeys - const updateComment = update[commentKey] + // Iterate over all known case string types + for (const key in caseStringTypes) { + const caseStringKey = key as CaseStringKeys + const updateCaseString = update[caseStringKey] - if (updateComment !== undefined) { - const commentType = explanatoryCommentTypes[commentKey] + if (updateCaseString !== undefined) { + const stringType = caseStringTypes[caseStringKey] - const comment = await this.explanatoryCommentModel.findOne({ - where: { caseId: theCase.id, commentType }, + const caseString = await this.caseStringModel.findOne({ + where: { caseId: theCase.id, stringType }, transaction, }) - if (comment) { - if (updateComment === null) { - await this.explanatoryCommentModel.destroy({ - where: { caseId: theCase.id, commentType }, + if (caseString) { + if (updateCaseString === null) { + await this.caseStringModel.destroy({ + where: { caseId: theCase.id, stringType }, transaction, }) } else { - await this.explanatoryCommentModel.update( - { comment: updateComment }, - { where: { caseId: theCase.id, commentType }, transaction }, + await this.caseStringModel.update( + { value: updateCaseString }, + { where: { caseId: theCase.id, stringType }, transaction }, ) } - } else if (updateComment !== null) { - await this.explanatoryCommentModel.create( - { caseId: theCase.id, commentType, comment: updateComment }, + } else if (updateCaseString !== null) { + await this.caseStringModel.create( + { caseId: theCase.id, stringType, value: updateCaseString }, { transaction }, ) } - delete update[commentKey] + delete update[caseStringKey] } } } diff --git a/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts b/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts index cc82fdad76c9..041b896821d5 100644 --- a/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/case/dto/updateCase.dto.ts @@ -513,4 +513,9 @@ export class UpdateCaseDto { @IsUUID() @ApiPropertyOptional({ type: String }) readonly mergeCaseId?: string + + @IsOptional() + @IsString() + @ApiPropertyOptional({ type: String }) + readonly civilDemands?: string } diff --git a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts index 857ce79b3907..4fdc06391c72 100644 --- a/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/case/guards/rolesRules.ts @@ -52,6 +52,7 @@ const prosecutorFields: (keyof UpdateCaseDto)[] = [ 'requestAppealRulingNotToBePublished', 'indictmentDeniedExplanation', 'indictmentReviewDecision', + 'civilDemands', ] const publicProsecutorFields: (keyof UpdateCaseDto)[] = ['indictmentReviewerId'] diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts new file mode 100644 index 000000000000..c22e624b71a1 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/case.interceptor.ts @@ -0,0 +1,38 @@ +import { map } from 'rxjs/operators' + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common' + +import { Case } from '../models/case.model' +import { CaseString } from '../models/caseString.model' + +const transformCase = (theCase: Case) => { + return { + ...theCase.toJSON(), + postponedIndefinitelyExplanation: + CaseString.postponedIndefinitelyExplanation(theCase.caseStrings), + civilDemands: CaseString.civilDemands(theCase.caseStrings), + } +} + +@Injectable() +export class CaseInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + return next.handle().pipe(map(transformCase)) + } +} + +@Injectable() +export class CasesInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + return next + .handle() + .pipe( + map((cases: Case[]) => cases.map((theCase) => transformCase(theCase))), + ) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts deleted file mode 100644 index 7dd3dfdf9e71..000000000000 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseFile.interceptor.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Observable } from 'rxjs' -import { map } from 'rxjs/operators' - -import { - CallHandler, - ExecutionContext, - Injectable, - NestInterceptor, -} from '@nestjs/common' - -import { - CaseAppealState, - CaseFileCategory, - isDefenceUser, - isIndictmentCase, - isPrisonStaffUser, - isPrisonSystemUser, - isRestrictionCase, - User, -} from '@island.is/judicial-system/types' - -import { Case } from '../models/case.model' - -@Injectable() -export class CaseFileInterceptor implements NestInterceptor { - intercept(context: ExecutionContext, next: CallHandler): Observable { - const request = context.switchToHttp().getRequest() - const user: User = request.user - - return next.handle().pipe( - map((data: Case) => { - if (isDefenceUser(user)) { - return data - } - - if ( - isPrisonStaffUser(user) || - (isRestrictionCase(data.type) && - data.appealState !== CaseAppealState.COMPLETED) - ) { - data.caseFiles?.splice(0, data.caseFiles.length) - } else if (isPrisonSystemUser(user)) { - data.caseFiles?.splice( - 0, - data.caseFiles.length, - ...data.caseFiles.filter((cf) => - isIndictmentCase(data.type) - ? cf.category === CaseFileCategory.RULING - : cf.category === CaseFileCategory.APPEAL_RULING, - ), - ) - } - - return data - }), - ) - } -} diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts index 291dda68112e..3044fdff06de 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseList.interceptor.ts @@ -10,8 +10,8 @@ import { import { IndictmentDecision } from '@island.is/judicial-system/types' import { Case } from '../models/case.model' +import { CaseString } from '../models/caseString.model' import { DateLog } from '../models/dateLog.model' -import { ExplanatoryComment } from '../models/explanatoryComment.model' @Injectable() export class CaseListInterceptor implements NestInterceptor { @@ -57,9 +57,7 @@ export class CaseListInterceptor implements NestInterceptor { appealRulingDecision: theCase.appealRulingDecision, prosecutorsOffice: theCase.prosecutorsOffice, postponedIndefinitelyExplanation: - ExplanatoryComment.postponedIndefinitelyExplanation( - theCase.explanatoryComments, - )?.comment, + CaseString.postponedIndefinitelyExplanation(theCase.caseStrings), indictmentReviewer: theCase.indictmentReviewer, indictmentReviewDecision: theCase.indictmentReviewDecision, indictmentDecision: theCase.indictmentDecision, diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseOriginalAncestor.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseOriginalAncestor.interceptor.ts index 35b092269a32..9920b3d6c278 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/caseOriginalAncestor.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/caseOriginalAncestor.interceptor.ts @@ -1,5 +1,3 @@ -import { Observable } from 'rxjs' - import { CallHandler, ExecutionContext, @@ -9,18 +7,16 @@ import { } from '@nestjs/common' import { InternalCaseService } from '../internalCase.service' +import { Case } from '../models/case.model' @Injectable() export class CaseOriginalAncestorInterceptor implements NestInterceptor { constructor(private readonly internalCaseService: InternalCaseService) {} - async intercept( - context: ExecutionContext, - next: CallHandler, - ): Promise> { + async intercept(context: ExecutionContext, next: CallHandler) { const request = context.switchToHttp().getRequest() - const theCase = request.case + const theCase: Case = request.case if (!theCase) { throw new InternalServerErrorException('Missing case') diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts index 5ff8d84bff3f..0eef4a4edc04 100644 --- a/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/completedAppealAccessed.interceptor.ts @@ -1,4 +1,3 @@ -import { Observable } from 'rxjs' import { map } from 'rxjs/operators' import { @@ -11,29 +10,29 @@ import { import { CaseAppealState, EventType, - InstitutionType, + isDefenceUser, + isPrisonStaffUser, + isProsecutionUser, User, - UserRole, } from '@island.is/judicial-system/types' import { EventLogService } from '../../event-log' -import { Case } from '../models/case.model' @Injectable() export class CompletedAppealAccessedInterceptor implements NestInterceptor { constructor(private readonly eventLogService: EventLogService) {} - intercept(context: ExecutionContext, next: CallHandler): Observable { + intercept(context: ExecutionContext, next: CallHandler) { const request = context.switchToHttp().getRequest() const user: User = request.user return next.handle().pipe( - map((data: Case) => { + map((data) => { if ( data.appealState === CaseAppealState.COMPLETED && - ([UserRole.PROSECUTOR, UserRole.DEFENDER].includes(user.role) || - (user.role === UserRole.PRISON_SYSTEM_STAFF && - user.institution?.type === InstitutionType.PRISON)) + (isProsecutionUser(user) || + isDefenceUser(user) || + isPrisonStaffUser(user)) ) { this.eventLogService.create({ eventType: EventType.APPEAL_RESULT_ACCESSED, diff --git a/apps/judicial-system/backend/src/app/modules/case/interceptors/limitedAccessCaseFile.interceptor.ts b/apps/judicial-system/backend/src/app/modules/case/interceptors/limitedAccessCaseFile.interceptor.ts new file mode 100644 index 000000000000..70d735d40e37 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/interceptors/limitedAccessCaseFile.interceptor.ts @@ -0,0 +1,36 @@ +import { map } from 'rxjs/operators' + +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common' + +import { CaseFileCategory, User } from '@island.is/judicial-system/types' + +import { canLimitedAcccessUserViewCaseFile } from '../../file' + +@Injectable() +export class LimitedAccessCaseFileInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler) { + const request = context.switchToHttp().getRequest() + const user: User = request.user + + return next.handle().pipe( + map((theCase) => { + const caseFiles = theCase.caseFiles?.filter( + ({ category }: { category: CaseFileCategory }) => + canLimitedAcccessUserViewCaseFile( + user, + theCase.type, + theCase.state, + category, + ), + ) + + return { ...theCase, caseFiles } + }), + ) + } +} diff --git a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts index 0d73789d2686..c81b939f4a16 100644 --- a/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/internalCase.controller.ts @@ -6,6 +6,7 @@ import { Param, Post, UseGuards, + UseInterceptors, } from '@nestjs/common' import { ApiCreatedResponse, ApiOkResponse, ApiTags } from '@nestjs/swagger' @@ -32,6 +33,10 @@ import { CurrentCase } from './guards/case.decorator' import { CaseCompletedGuard } from './guards/caseCompleted.guard' import { CaseExistsGuard } from './guards/caseExists.guard' import { CaseTypeGuard } from './guards/caseType.guard' +import { + CaseInterceptor, + CasesInterceptor, +} from './interceptors/case.interceptor' import { ArchiveResponse } from './models/archive.response' import { Case } from './models/case.model' import { DeliverResponse } from './models/deliver.response' @@ -47,6 +52,7 @@ export class InternalCaseController { @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} + @UseInterceptors(CaseInterceptor) @Post('case') @ApiCreatedResponse({ type: Case, description: 'Creates a new case' }) async create(@Body() caseToCreate: InternalCreateCaseDto): Promise { @@ -76,6 +82,7 @@ export class InternalCaseController { isArray: true, description: 'Gets all indictment cases', }) + @UseInterceptors(CasesInterceptor) getIndictmentCases( @Body() internalCasesDto: InternalCasesDto, ): Promise { @@ -86,15 +93,16 @@ export class InternalCaseController { ) } - @Post('cases/indictment/:caseId') + @Post('case/indictment/:caseId') @ApiOkResponse({ type: Case, description: 'Gets indictment case by id', }) + @UseInterceptors(CaseInterceptor) getIndictmentCase( @Param('caseId') caseId: string, @Body() internalCasesDto: InternalCasesDto, - ): Promise { + ): Promise { this.logger.debug(`Getting indictment case ${caseId}`) return this.internalCaseService.getIndictmentCase( 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 235d74f860a0..741699620065 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 @@ -64,9 +64,9 @@ import { archiveFilter } from './filters/case.archiveFilter' import { ArchiveResponse } from './models/archive.response' import { Case } from './models/case.model' import { CaseArchive } from './models/caseArchive.model' +import { CaseString } from './models/caseString.model' import { DateLog } from './models/dateLog.model' import { DeliverResponse } from './models/deliver.response' -import { ExplanatoryComment } from './models/explanatoryComment.model' import { caseModuleConfig } from './case.config' import { PdfService } from './pdf.service' @@ -119,9 +119,7 @@ const indictmentCountEncryptionProperties: (keyof IndictmentCount)[] = [ 'legalArguments', ] -const explanatoryCommentEncryptionProperties: (keyof ExplanatoryComment)[] = [ - 'comment', -] +const caseStringEncryptionProperties: (keyof CaseString)[] = ['value'] const collectEncryptionProperties = ( properties: string[], @@ -148,8 +146,8 @@ export class InternalCaseService { constructor( @InjectConnection() private readonly sequelize: Sequelize, - @InjectModel(ExplanatoryComment) - private readonly explanatoryCommentModel: typeof ExplanatoryComment, + @InjectModel(CaseString) + private readonly caseStringModel: typeof CaseString, @InjectModel(Case) private readonly caseModel: typeof Case, @InjectModel(CaseArchive) private readonly caseArchiveModel: typeof CaseArchive, @@ -393,17 +391,13 @@ export class InternalCaseService { { model: Defendant, as: 'defendants' }, { model: IndictmentCount, as: 'indictmentCounts' }, { model: CaseFile, as: 'caseFiles' }, - { model: ExplanatoryComment, as: 'explanatoryComments' }, + { model: CaseString, as: 'caseStrings' }, ], order: [ [{ model: Defendant, as: 'defendants' }, 'created', 'ASC'], [{ model: IndictmentCount, as: 'indictmentCounts' }, 'created', 'ASC'], [{ model: CaseFile, as: 'caseFiles' }, 'created', 'ASC'], - [ - { model: ExplanatoryComment, as: 'explanatoryComments' }, - 'created', - 'ASC', - ], + [{ model: CaseString, as: 'caseStrings' }, 'created', 'ASC'], ], where: archiveFilter, }) @@ -463,19 +457,19 @@ export class InternalCaseService { ) } - const explanatoryCommentsArchive = [] - for (const comment of theCase.explanatoryComments ?? []) { - const [clearedExplanatoryCommentProperties, explanatoryCommentArchive] = + const caseStringsArchive = [] + for (const caseString of theCase.caseStrings ?? []) { + const [clearedCaseStringProperties, caseStringArchive] = collectEncryptionProperties( - explanatoryCommentEncryptionProperties, - comment, + caseStringEncryptionProperties, + caseString, ) - explanatoryCommentsArchive.push(explanatoryCommentArchive) + caseStringsArchive.push(caseStringArchive) - await this.explanatoryCommentModel.update( - clearedExplanatoryCommentProperties, - { where: { id: comment.id, caseId: theCase.id }, transaction }, - ) + await this.caseStringModel.update(clearedCaseStringProperties, { + where: { id: caseString.id, caseId: theCase.id }, + transaction, + }) } await this.caseArchiveModel.create( @@ -487,7 +481,7 @@ export class InternalCaseService { defendants: defendantsArchive, caseFiles: caseFilesArchive, indictmentCounts: indictmentCountsArchive, - explanatoryComments: explanatoryCommentsArchive, + caseStrings: caseStringsArchive, }), this.config.archiveEncryptionKey, { iv: CryptoJS.enc.Hex.parse(uuidFactory()) }, @@ -1234,10 +1228,7 @@ export class InternalCaseService { }) } - async getIndictmentCase( - caseId: string, - nationalId: string, - ): Promise { + async getIndictmentCase(caseId: string, nationalId: string): Promise { // The national id could be without a hyphen or with a hyphen so we need to // search for both const formattedNationalId = formatNationalId(nationalId) diff --git a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts index 42c90d58ec5e..4663b613d9a8 100644 --- a/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/case/limitedAccessCase.controller.ts @@ -53,8 +53,9 @@ import { CaseWriteGuard } from './guards/caseWrite.guard' import { LimitedAccessCaseExistsGuard } from './guards/limitedAccessCaseExists.guard' import { RequestSharedWithDefenderGuard } from './guards/requestSharedWithDefender.guard' import { defenderTransitionRule, defenderUpdateRule } from './guards/rolesRules' -import { CaseFileInterceptor } from './interceptors/caseFile.interceptor' +import { CaseInterceptor } from './interceptors/case.interceptor' import { CompletedAppealAccessedInterceptor } from './interceptors/completedAppealAccessed.interceptor' +import { LimitedAccessCaseFileInterceptor } from './interceptors/limitedAccessCaseFile.interceptor' import { Case } from './models/case.model' import { transitionCase } from './state/case.state' import { @@ -81,12 +82,16 @@ export class LimitedAccessCaseController { CaseReadGuard, ) @RolesRules(prisonSystemStaffRule, defenderRule) + @UseInterceptors( + CompletedAppealAccessedInterceptor, + LimitedAccessCaseFileInterceptor, + CaseInterceptor, + ) @Get('case/:caseId/limitedAccess') @ApiOkResponse({ type: Case, description: 'Gets a limited set of properties of an existing case', }) - @UseInterceptors(CompletedAppealAccessedInterceptor, CaseFileInterceptor) async getById( @Param('caseId') caseId: string, @CurrentCase() theCase: Case, @@ -115,6 +120,7 @@ export class LimitedAccessCaseController { CaseCompletedGuard, ) @RolesRules(defenderUpdateRule) + @UseInterceptors(CaseInterceptor) @Patch('case/:caseId/limitedAccess') @ApiOkResponse({ type: Case, description: 'Updates an existing case' }) update( @@ -143,6 +149,7 @@ export class LimitedAccessCaseController { CaseCompletedGuard, ) @RolesRules(defenderTransitionRule) + @UseInterceptors(CaseInterceptor) @Patch('case/:caseId/limitedAccess/state') @ApiOkResponse({ type: Case, 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 691c040563a7..0f784551d1f7 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 @@ -21,10 +21,10 @@ import { CaseFileCategory, CaseFileState, CaseState, - CommentType, DateType, EventType, NotificationType, + StringType, UserRole, } from '@island.is/judicial-system/types' @@ -39,8 +39,8 @@ import { import { Institution } from '../institution' import { User } from '../user' import { Case } from './models/case.model' +import { CaseString } from './models/caseString.model' import { DateLog } from './models/dateLog.model' -import { ExplanatoryComment } from './models/explanatoryComment.model' import { PdfService } from './pdf.service' export const attributes: (keyof Case)[] = [ @@ -116,7 +116,7 @@ export interface LimitedAccessUpdateCase const eventTypes = Object.values(EventType) const dateTypes = Object.values(DateType) -const commentTypes = Object.values(CommentType) +const stringTypes = Object.values(StringType) export const include: Includeable[] = [ { model: Institution, as: 'prosecutorsOffice' }, @@ -191,7 +191,6 @@ export const include: Includeable[] = [ CaseFileCategory.CRIMINAL_RECORD, CaseFileCategory.COST_BREAKDOWN, CaseFileCategory.CASE_FILE, - CaseFileCategory.CASE_FILE_RECORD, CaseFileCategory.PROSECUTOR_CASE_FILE, CaseFileCategory.DEFENDANT_CASE_FILE, ], @@ -212,10 +211,10 @@ export const include: Includeable[] = [ where: { dateType: { [Op.in]: dateTypes } }, }, { - model: ExplanatoryComment, - as: 'explanatoryComments', + model: CaseString, + as: 'caseStrings', required: false, - where: { commentType: { [Op.in]: commentTypes } }, + where: { stringType: { [Op.in]: stringTypes } }, }, ] diff --git a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts index 96453bfa9e01..bd26c92f2267 100644 --- a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts @@ -44,8 +44,8 @@ import { IndictmentCount } from '../../indictment-count' import { Institution } from '../../institution' import { Notification } from '../../notification' import { User } from '../../user' +import { CaseString } from './caseString.model' import { DateLog } from './dateLog.model' -import { ExplanatoryComment } from './explanatoryComment.model' @Table({ tableName: 'case', @@ -901,11 +901,11 @@ export class Case extends Model { dateLogs?: DateLog[] /********** - * The case's explanatory comments + * The case's strings **********/ - @HasMany(() => ExplanatoryComment, 'caseId') - @ApiPropertyOptional({ type: ExplanatoryComment, isArray: true }) - explanatoryComments?: ExplanatoryComment[] + @HasMany(() => CaseString, 'caseId') + @ApiPropertyOptional({ type: CaseString, isArray: true }) + caseStrings?: CaseString[] /********** * The appeal ruling expiration date and time - example: the end of custody in custody cases - diff --git a/apps/judicial-system/backend/src/app/modules/case/models/explanatoryComment.model.ts b/apps/judicial-system/backend/src/app/modules/case/models/caseString.model.ts similarity index 53% rename from apps/judicial-system/backend/src/app/modules/case/models/explanatoryComment.model.ts rename to apps/judicial-system/backend/src/app/modules/case/models/caseString.model.ts index 45a6b0cc3b79..38c4b4ac37af 100644 --- a/apps/judicial-system/backend/src/app/modules/case/models/explanatoryComment.model.ts +++ b/apps/judicial-system/backend/src/app/modules/case/models/caseString.model.ts @@ -10,23 +10,26 @@ import { import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' -import { CommentType } from '@island.is/judicial-system/types' +import { StringType } from '@island.is/judicial-system/types' import { Case } from './case.model' @Table({ - tableName: 'explanatory_comment', + tableName: 'case_string', timestamps: true, }) -export class ExplanatoryComment extends Model { - static postponedIndefinitelyExplanation( - explanatoryComments?: ExplanatoryComment[], - ) { - return explanatoryComments?.find( - (explanatoryComment) => - explanatoryComment.commentType === - CommentType.POSTPONED_INDEFINITELY_EXPLANATION, - ) +export class CaseString extends Model { + static postponedIndefinitelyExplanation(caseStrings?: CaseString[]) { + return caseStrings?.find( + (caseString) => + caseString.stringType === StringType.POSTPONED_INDEFINITELY_EXPLANATION, + )?.value + } + + static civilDemands(caseStrings?: CaseString[]) { + return caseStrings?.find( + (caseString) => caseString.stringType === StringType.CIVIL_DEMANDS, + )?.value } @Column({ @@ -49,17 +52,17 @@ export class ExplanatoryComment extends Model { @Column({ type: DataType.ENUM, allowNull: false, - values: Object.values(CommentType), + values: Object.values(StringType), }) - @ApiProperty({ enum: CommentType }) - commentType!: CommentType + @ApiProperty({ enum: StringType }) + stringType!: StringType @ForeignKey(() => Case) @Column({ type: DataType.UUID, allowNull: false }) @ApiPropertyOptional({ type: String }) caseId!: string - @Column({ type: DataType.STRING, allowNull: false }) + @Column({ type: DataType.TEXT, allowNull: false }) @ApiPropertyOptional({ type: String }) - comment!: string + value!: string } 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 c282283ddfad..0ffde6ad16ca 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 @@ -9,13 +9,13 @@ import { CaseOrigin, CaseState, CaseType, - CommentType, DateType, indictmentCases, InstitutionType, investigationCases, NotificationType, restrictionCases, + StringType, User, UserRole, } from '@island.is/judicial-system/types' @@ -28,8 +28,8 @@ import { FileService } from '../../../file' import { UserService } from '../../../user' import { UpdateCaseDto } from '../../dto/updateCase.dto' import { Case } from '../../models/case.model' +import { CaseString } from '../../models/caseString.model' import { DateLog } from '../../models/dateLog.model' -import { ExplanatoryComment } from '../../models/explanatoryComment.model' jest.mock('../../../../factories') @@ -73,7 +73,7 @@ describe('CaseController - Update', () => { let transaction: Transaction let mockCaseModel: typeof Case let mockDateLogModel: typeof DateLog - let mockExplanatoryCommentModel: typeof ExplanatoryComment + let mockCaseStringModel: typeof CaseString let givenWhenThen: GivenWhenThen beforeEach(async () => { @@ -84,7 +84,7 @@ describe('CaseController - Update', () => { sequelize, caseModel, dateLogModel, - explanatoryCommentModel, + caseStringModel, caseController, } = await createTestingCaseModule() @@ -93,7 +93,7 @@ describe('CaseController - Update', () => { mockFileService = fileService mockCaseModel = caseModel mockDateLogModel = dateLogModel - mockExplanatoryCommentModel = explanatoryCommentModel + mockCaseStringModel = caseStringModel const mockTransaction = sequelize.transaction as jest.Mock transaction = {} as Transaction @@ -913,11 +913,31 @@ describe('CaseController - Update', () => { }) it('should update case', () => { - expect(mockExplanatoryCommentModel.create).toHaveBeenCalledWith( + expect(mockCaseStringModel.create).toHaveBeenCalledWith( { - commentType: CommentType.POSTPONED_INDEFINITELY_EXPLANATION, + stringType: StringType.POSTPONED_INDEFINITELY_EXPLANATION, caseId, - comment: postponedIndefinitelyExplanation, + value: postponedIndefinitelyExplanation, + }, + { transaction }, + ) + }) + }) + + describe('civil demands updated', () => { + const civilDemands = uuid() + const caseToUpdate = { civilDemands } + + beforeEach(async () => { + await givenWhenThen(caseId, user, theCase, caseToUpdate) + }) + + it('should update case', () => { + expect(mockCaseStringModel.create).toHaveBeenCalledWith( + { + stringType: StringType.CIVIL_DEMANDS, + caseId, + value: civilDemands, }, { transaction }, ) diff --git a/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts b/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts index 77035ca9a36c..1e8dd996ea45 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/createTestingCaseModule.ts @@ -33,8 +33,8 @@ import { LimitedAccessCaseController } from '../limitedAccessCase.controller' import { LimitedAccessCaseService } from '../limitedAccessCase.service' import { Case } from '../models/case.model' import { CaseArchive } from '../models/caseArchive.model' +import { CaseString } from '../models/caseString.model' import { DateLog } from '../models/dateLog.model' -import { ExplanatoryComment } from '../models/explanatoryComment.model' import { PdfService } from '../pdf.service' jest.mock('@island.is/judicial-system/message') @@ -114,7 +114,7 @@ export const createTestingCaseModule = async () => { }, }, { - provide: getModelToken(ExplanatoryComment), + provide: getModelToken(CaseString), useValue: { create: jest.fn(), findOne: jest.fn(), @@ -164,8 +164,8 @@ export const createTestingCaseModule = async () => { const dateLogModel = caseModule.get(getModelToken(DateLog)) - const explanatoryCommentModel = caseModule.get( - getModelToken(ExplanatoryComment), + const caseStringModel = caseModule.get( + getModelToken(CaseString), ) const caseConfig = caseModule.get>( @@ -203,7 +203,7 @@ export const createTestingCaseModule = async () => { caseModel, caseArchiveModel, dateLogModel, - explanatoryCommentModel, + caseStringModel, caseConfig, caseService, limitedAccessCaseService, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archive.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archive.spec.ts index 5db947012958..d6beb070a348 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archive.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/internalCaseController/archive.spec.ts @@ -20,7 +20,7 @@ import { archiveFilter } from '../../filters/case.archiveFilter' import { ArchiveResponse } from '../../models/archive.response' import { Case } from '../../models/case.model' import { CaseArchive } from '../../models/caseArchive.model' -import { ExplanatoryComment } from '../../models/explanatoryComment.model' +import { CaseString } from '../../models/caseString.model' jest.mock('crypto-js') jest.mock('../../../../factories') @@ -36,7 +36,7 @@ describe('InternalCaseController - Archive', () => { let mockFileService: FileService let mockDefendantService: DefendantService let mockIndictmentCountService: IndictmentCountService - let mockExplanatoryCommentModel: typeof ExplanatoryComment + let mockCaseStringModel: typeof CaseString let mockCaseModel: typeof Case let mockCaseArchiveModel: typeof CaseArchive let mockCaseConfig: ConfigType @@ -49,7 +49,7 @@ describe('InternalCaseController - Archive', () => { defendantService, indictmentCountService, sequelize, - explanatoryCommentModel, + caseStringModel, caseModel, caseArchiveModel, caseConfig, @@ -59,7 +59,7 @@ describe('InternalCaseController - Archive', () => { mockFileService = fileService mockDefendantService = defendantService mockIndictmentCountService = indictmentCountService - mockExplanatoryCommentModel = explanatoryCommentModel + mockCaseStringModel = caseStringModel mockCaseModel = caseModel mockCaseArchiveModel = caseArchiveModel mockCaseConfig = caseConfig @@ -90,8 +90,8 @@ describe('InternalCaseController - Archive', () => { const caseFileId2 = uuid() const indictmentCountId1 = uuid() const indictmentCountId2 = uuid() - const explanatoryCommentId1 = uuid() - const explanatoryCommentId2 = uuid() + const caseStringId1 = uuid() + const caseStringId2 = uuid() const theCase = { id: caseId, description: 'original_description', @@ -168,9 +168,9 @@ describe('InternalCaseController - Archive', () => { indictmentDeniedExplanation: 'original_indictment_denied_explanation', indictmentReturnedExplanation: 'original_indictment_returned_explanation', isArchived: false, - explanatoryComments: [ - { id: explanatoryCommentId1, comment: 'original_comment1' }, - { id: explanatoryCommentId2, comment: 'original_comment2' }, + caseStrings: [ + { id: caseStringId1, value: 'original_comment1' }, + { id: caseStringId2, value: 'original_comment2' }, ], } const archive = JSON.stringify({ @@ -240,9 +240,9 @@ describe('InternalCaseController - Archive', () => { legalArguments: 'original_legal_arguments2', }, ], - explanatoryComments: [ - { comment: 'original_comment1' }, - { comment: 'original_comment2' }, + caseStrings: [ + { value: 'original_comment1' }, + { value: 'original_comment2' }, ], }) const iv = uuid() @@ -251,9 +251,8 @@ describe('InternalCaseController - Archive', () => { let then: Then beforeEach(async () => { - const mockUpdateExplanatoryComment = - mockExplanatoryCommentModel.update as jest.Mock - mockUpdateExplanatoryComment.mockResolvedValueOnce([1]) + const mockUpdateCaseString = mockCaseStringModel.update as jest.Mock + mockUpdateCaseString.mockResolvedValueOnce([1]) const mockFindOne = mockCaseModel.findOne as jest.Mock mockFindOne.mockResolvedValueOnce(theCase) const mockUpdate = mockCaseModel.update as jest.Mock @@ -274,7 +273,7 @@ describe('InternalCaseController - Archive', () => { { model: Defendant, as: 'defendants' }, { model: IndictmentCount, as: 'indictmentCounts' }, { model: CaseFile, as: 'caseFiles' }, - { model: ExplanatoryComment, as: 'explanatoryComments' }, + { model: CaseString, as: 'caseStrings' }, ], order: [ [{ model: Defendant, as: 'defendants' }, 'created', 'ASC'], @@ -284,11 +283,7 @@ describe('InternalCaseController - Archive', () => { 'ASC', ], [{ model: CaseFile, as: 'caseFiles' }, 'created', 'ASC'], - [ - { model: ExplanatoryComment, as: 'explanatoryComments' }, - 'created', - 'ASC', - ], + [{ model: CaseString, as: 'caseStrings' }, 'created', 'ASC'], ], where: archiveFilter, }) @@ -336,13 +331,13 @@ describe('InternalCaseController - Archive', () => { }, transaction, ) - expect(mockExplanatoryCommentModel.update).toHaveBeenCalledWith( - { comment: '' }, - { where: { id: explanatoryCommentId1, caseId }, transaction }, + expect(mockCaseStringModel.update).toHaveBeenCalledWith( + { value: '' }, + { where: { id: caseStringId1, caseId }, transaction }, ) - expect(mockExplanatoryCommentModel.update).toHaveBeenCalledWith( - { comment: '' }, - { where: { id: explanatoryCommentId2, caseId }, transaction }, + expect(mockCaseStringModel.update).toHaveBeenCalledWith( + { value: '' }, + { where: { id: caseStringId2, caseId }, transaction }, ) expect(CryptoJS.enc.Hex.parse).toHaveBeenCalledWith(iv) expect(CryptoJS.AES.encrypt).toHaveBeenCalledWith( diff --git a/apps/judicial-system/backend/src/app/modules/event/event.service.ts b/apps/judicial-system/backend/src/app/modules/event/event.service.ts index e66825a614af..aeac0ff7f26d 100644 --- a/apps/judicial-system/backend/src/app/modules/event/event.service.ts +++ b/apps/judicial-system/backend/src/app/modules/event/event.service.ts @@ -18,8 +18,8 @@ import { } from '@island.is/judicial-system/types' import { type Case } from '../case' +import { CaseString } from '../case/models/caseString.model' import { DateLog } from '../case/models/dateLog.model' -import { ExplanatoryComment } from '../case/models/explanatoryComment.model' import { eventModuleConfig } from './event.config' const errorEmojis = [ @@ -121,15 +121,11 @@ export class EventService { }\n>Dómritari ${ theCase.registrar?.name ?? 'er ekki skráður' }\n>Fyrirtaka ${ - ExplanatoryComment.postponedIndefinitelyExplanation( - theCase.explanatoryComments, - ) - ? 'ekki ákveðin' - : formatDate( - DateLog.courtDate(theCase.dateLogs)?.date ?? - DateLog.arraignmentDate(theCase.dateLogs)?.date, - 'Pp', - ) ?? 'er ekki skráð' + formatDate( + DateLog.courtDate(theCase.dateLogs)?.date ?? + DateLog.arraignmentDate(theCase.dateLogs)?.date, + 'Pp', + ) ?? 'er ekki skráð' }` : '' diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts index 60f536547067..020af77d5a4f 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/caseFileCategory.ts @@ -1,4 +1,14 @@ -import { CaseFileCategory } from '@island.is/judicial-system/types' +import { + CaseFileCategory, + CaseState, + CaseType, + isCompletedCase, + isDefenceUser, + isIndictmentCase, + isPrisonAdminUser, + isRequestCase, + User, +} from '@island.is/judicial-system/types' export const defenderCaseFileCategoriesForRestrictionAndInvestigationCases = [ CaseFileCategory.PROSECUTOR_APPEAL_BRIEF, @@ -12,7 +22,7 @@ export const defenderCaseFileCategoriesForRestrictionAndInvestigationCases = [ CaseFileCategory.APPEAL_COURT_RECORD, ] -export const defenderCaseFileCategoriesForIndictmentCases = [ +const defenderCaseFileCategoriesForIndictmentCases = [ CaseFileCategory.COURT_RECORD, CaseFileCategory.RULING, CaseFileCategory.INDICTMENT, @@ -23,7 +33,47 @@ export const defenderCaseFileCategoriesForIndictmentCases = [ CaseFileCategory.DEFENDANT_CASE_FILE, ] -export const prisonAdminCaseFileCategories = [ +const prisonAdminCaseFileCategories = [ CaseFileCategory.APPEAL_RULING, CaseFileCategory.RULING, ] + +export const canLimitedAcccessUserViewCaseFile = ( + user: User, + caseType: CaseType, + caseState: CaseState, + caseFileCategory?: CaseFileCategory, +) => { + if (!caseFileCategory) { + return false + } + + if (isDefenceUser(user)) { + if ( + isRequestCase(caseType) && + isCompletedCase(caseState) && + defenderCaseFileCategoriesForRestrictionAndInvestigationCases.includes( + caseFileCategory, + ) + ) { + return true + } + + if ( + isIndictmentCase(caseType) && + defenderCaseFileCategoriesForIndictmentCases.includes(caseFileCategory) + ) { + return true + } + } + + if ( + isPrisonAdminUser(user) && + isCompletedCase(caseState) && + prisonAdminCaseFileCategories.includes(caseFileCategory) + ) { + return true + } + + return false +} diff --git a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts index a8c2f8295ea7..b9bdf659e23b 100644 --- a/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts +++ b/apps/judicial-system/backend/src/app/modules/file/guards/limitedAccessViewCaseFile.guard.ts @@ -6,22 +6,11 @@ import { InternalServerErrorException, } from '@nestjs/common' -import { - isCompletedCase, - isDefenceUser, - isIndictmentCase, - isPrisonAdminUser, - isRequestCase, - User, -} from '@island.is/judicial-system/types' +import { User } from '@island.is/judicial-system/types' import { Case } from '../../case' import { CaseFile } from '../models/file.model' -import { - defenderCaseFileCategoriesForIndictmentCases, - defenderCaseFileCategoriesForRestrictionAndInvestigationCases, - prisonAdminCaseFileCategories, -} from './caseFileCategory' +import { canLimitedAcccessUserViewCaseFile } from './caseFileCategory' @Injectable() export class LimitedAccessViewCaseFileGuard implements CanActivate { @@ -46,30 +35,13 @@ export class LimitedAccessViewCaseFileGuard implements CanActivate { throw new InternalServerErrorException('Missing case file') } - if (isDefenceUser(user) && caseFile.category) { - if ( - isRequestCase(theCase.type) && - isCompletedCase(theCase.state) && - defenderCaseFileCategoriesForRestrictionAndInvestigationCases.includes( - caseFile.category, - ) - ) { - return true - } - - if ( - isIndictmentCase(theCase.type) && - defenderCaseFileCategoriesForIndictmentCases.includes(caseFile.category) - ) { - return true - } - } - if ( - caseFile.category && - isCompletedCase(theCase.state) && - isPrisonAdminUser(user) && - prisonAdminCaseFileCategories.includes(caseFile.category) + canLimitedAcccessUserViewCaseFile( + user, + theCase.type, + theCase.state, + caseFile.category, + ) ) { return true } diff --git a/apps/judicial-system/backend/src/app/modules/file/index.ts b/apps/judicial-system/backend/src/app/modules/file/index.ts index 9b52be8a344c..34a68700f3fc 100644 --- a/apps/judicial-system/backend/src/app/modules/file/index.ts +++ b/apps/judicial-system/backend/src/app/modules/file/index.ts @@ -1,3 +1,6 @@ export { CaseFile } from './models/file.model' export { FileService } from './file.service' -export { defenderCaseFileCategoriesForRestrictionAndInvestigationCases } from './guards/caseFileCategory' +export { + canLimitedAcccessUserViewCaseFile, + defenderCaseFileCategoriesForRestrictionAndInvestigationCases, +} from './guards/caseFileCategory' diff --git a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts index 660bb3e5ff7b..919a6f7e4fed 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/modules/cases/case.service.ts @@ -190,7 +190,7 @@ export class CaseService { ): Promise { try { const res = await fetch( - `${this.config.backendUrl}/api/internal/cases/indictment/${id}`, + `${this.config.backendUrl}/api/internal/case/indictment/${id}`, { method: 'POST', headers: { diff --git a/apps/judicial-system/web/src/components/FormProvider/case.graphql b/apps/judicial-system/web/src/components/FormProvider/case.graphql index 96bd2420103f..1c9635d77d2c 100644 --- a/apps/judicial-system/web/src/components/FormProvider/case.graphql +++ b/apps/judicial-system/web/src/components/FormProvider/case.graphql @@ -308,5 +308,6 @@ query Case($input: CaseQueryInput!) { policeCaseNumbers indictmentSubtypes } + civilDemands } } diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.strings.ts b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.strings.ts index 6e9583784fda..2eb35ef19d13 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.strings.ts +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.strings.ts @@ -94,4 +94,22 @@ export const indictment = defineMessages({ defaultMessage: 'Ákæra - PDF', description: 'Notaður sem texti á hnappi til að sækja ákæru sem PDF skjal.', }, + civilDemandsTitle: { + id: 'judicial.system.core:indictments_indictment.civil_demands_title', + defaultMessage: 'Einkaréttarkrafa', + description: + 'Notaður sem titill á Einkaréttarkrafa svæði á ákæra skrefi í ákærum.', + }, + civilDemandsLabel: { + id: 'judicial.system.core:indictments_indictment.civil_demands_label', + defaultMessage: 'Einkaréttarkrafa', + description: + 'Notaður sem titill á Einkaréttarkrafa textasvæði á ákæra skrefi í ákærum.', + }, + civilDemandsPlaceholder: { + id: 'judicial.system.core:indictments_indictment.civil_demands_placeholder', + defaultMessage: 'Hver er krafa kröfuhafa?', + description: + 'Notaður sem skýritexti á Einkaréttarkrafa textasvæði á ákæra skrefi í ákærum.', + }, }) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx index a2cc60c4ff1c..b0b95552c0e3 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Indictment/Indictment.tsx @@ -97,7 +97,8 @@ const Indictment = () => { indictmentIntroductionErrorMessage, setIndictmentIntroductionErrorMessage, ] = useState('') - const [demandsErrorMessage, setDemandsErrorMessage] = useState('') + const [demandsErrorMessage, setDemandsErrorMessage] = useState('') + const [civilDemandsErrorMessage, setCivilDemandsErrorMessage] = useState('') const { data: policeCaseData } = usePoliceCaseInfoQuery({ variables: { @@ -426,7 +427,7 @@ const Indictment = () => { name="demands" label={formatMessage(strings.demandsLabel)} placeholder={formatMessage(strings.demandsPlaceholder)} - value={workingCase.demands || ''} + value={workingCase.demands ?? ''} errorMessage={demandsErrorMessage} hasError={demandsErrorMessage !== ''} onChange={(event) => @@ -457,6 +458,44 @@ const Indictment = () => { /> + + + + + removeTabsValidateAndSet( + 'civilDemands', + event.target.value, + ['empty'], + setWorkingCase, + civilDemandsErrorMessage, + setCivilDemandsErrorMessage, + ) + } + onBlur={(event) => + validateAndSendToServer( + 'civilDemands', + event.target.value, + ['empty'], + workingCase, + updateCase, + setCivilDemandsErrorMessage, + ) + } + textarea + autoComplete="off" + required + rows={7} + autoExpand={{ on: true, maxHeight: 300 }} + /> + + { - return Boolean(workingCase.demands) + return Boolean(workingCase.demands && workingCase.civilDemands) } export const isPoliceDemandsStepValidRC = (workingCase: Case): boolean => { diff --git a/libs/judicial-system/types/src/index.ts b/libs/judicial-system/types/src/index.ts index 772c1463302e..9694c67c1755 100644 --- a/libs/judicial-system/types/src/index.ts +++ b/libs/judicial-system/types/src/index.ts @@ -12,7 +12,7 @@ export { NotificationType } from './lib/notification' export type { Institution } from './lib/institution' export { EventType } from './lib/eventLog' export { DateType } from './lib/dateLog' -export { CommentType } from './lib/comment' +export { StringType } from './lib/caseString' export { CaseFileState, CaseFileCategory } from './lib/file' diff --git a/libs/judicial-system/types/src/lib/comment.ts b/libs/judicial-system/types/src/lib/caseString.ts similarity index 56% rename from libs/judicial-system/types/src/lib/comment.ts rename to libs/judicial-system/types/src/lib/caseString.ts index 2b8f1cbaf20b..2f78b27d1577 100644 --- a/libs/judicial-system/types/src/lib/comment.ts +++ b/libs/judicial-system/types/src/lib/caseString.ts @@ -1,3 +1,4 @@ -export enum CommentType { +export enum StringType { POSTPONED_INDEFINITELY_EXPLANATION = 'POSTPONED_INDEFINITELY_EXPLANATION', + CIVIL_DEMANDS = 'CIVIL_DEMANDS', }