diff --git a/apps/judicial-system/api/src/app/app.module.ts b/apps/judicial-system/api/src/app/app.module.ts index 2b455495e504..881bb2b8e7c7 100644 --- a/apps/judicial-system/api/src/app/app.module.ts +++ b/apps/judicial-system/api/src/app/app.module.ts @@ -22,6 +22,7 @@ import { DefendantModule, DefenderModule, defenderModuleConfig, + EventLogModule, FeatureModule, featureModuleConfig, FileModule, @@ -67,6 +68,7 @@ const autoSchemaFile = environment.production FeatureModule, CmsTranslationsModule, PoliceModule, + EventLogModule, ProblemModule.forRoot({ logAllErrors: true }), ConfigModule.forRoot({ isGlobal: true, diff --git a/apps/judicial-system/api/src/app/data-sources/backend.ts b/apps/judicial-system/api/src/app/data-sources/backend.ts index cf215bcfb90d..215f3f02e286 100644 --- a/apps/judicial-system/api/src/app/data-sources/backend.ts +++ b/apps/judicial-system/api/src/app/data-sources/backend.ts @@ -9,6 +9,7 @@ import { CommentType, DateType, type User, + UserRole, } from '@island.is/judicial-system/types' import { environment } from '../../environments' @@ -20,6 +21,7 @@ import { } from '../modules/case' import { CaseListEntry } from '../modules/case-list' import { Defendant, DeleteDefendantResponse } from '../modules/defendant' +import { CreateEventLogInput } from '../modules/event-log' import { CaseFile, DeleteFileResponse, @@ -407,6 +409,20 @@ export class BackendApi extends DataSource<{ req: Request }> { limitedAccessGetAllFiles(caseId: string): Promise { return this.get(`case/${caseId}/limitedAccess/files/all`) } + + createEventLog(eventLog: CreateEventLogInput, userRole?: UserRole) { + return fetch(`${environment.backend.url}/api/eventLog/event`, { + method: 'POST', + headers: { + authorization: `Bearer ${environment.auth.secretToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + ...eventLog, + userRole, + }), + }) + } } export default BackendApi diff --git a/apps/judicial-system/api/src/app/modules/auth/auth.service.ts b/apps/judicial-system/api/src/app/modules/auth/auth.service.ts index 9f2ea5e7b278..f307d75ad7d0 100644 --- a/apps/judicial-system/api/src/app/modules/auth/auth.service.ts +++ b/apps/judicial-system/api/src/app/modules/auth/auth.service.ts @@ -170,7 +170,7 @@ export class AuthService { nationalId: string, userRole?: UserRole, ) { - await fetch(`${this.config.backendUrl}/api/event-log/log-event`, { + await fetch(`${this.config.backendUrl}/api/eventLog/event`, { method: 'POST', headers: { authorization: `Bearer ${this.config.secretToken}`, 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 9973ee3a7191..57f588f648dd 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 @@ -26,11 +26,11 @@ import { } from '@island.is/judicial-system/types' import { Defendant } from '../../defendant' +import { EventLog } from '../../event-log' import { CaseFile } from '../../file' import { IndictmentCount } from '../../indictment-count' import { Institution } from '../../institution' import { User } from '../../user' -import { EventLog } from './eventLog.model' import { Notification } from './notification.model' registerEnumType(CaseOrigin, { name: 'CaseOrigin' }) diff --git a/apps/judicial-system/api/src/app/modules/event-log/dto/createEventLog.input.ts b/apps/judicial-system/api/src/app/modules/event-log/dto/createEventLog.input.ts new file mode 100644 index 000000000000..9e3e7fcb99ff --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/event-log/dto/createEventLog.input.ts @@ -0,0 +1,22 @@ +import { Allow, IsOptional } from 'class-validator' + +import { Field, InputType } from '@nestjs/graphql' + +import { EventType } from '@island.is/judicial-system/types' + +@InputType() +export class CreateEventLogInput { + @Allow() + @Field(() => EventType) + readonly eventType!: EventType + + @Allow() + @IsOptional() + @Field(() => String, { nullable: true }) + readonly caseId?: string + + @Allow() + @IsOptional() + @Field(() => String, { nullable: true }) + readonly nationalId?: string +} diff --git a/apps/judicial-system/api/src/app/modules/event-log/eventLog.config.ts b/apps/judicial-system/api/src/app/modules/event-log/eventLog.config.ts new file mode 100644 index 000000000000..b96be6379b54 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/event-log/eventLog.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from '@island.is/nest/config' + +export const eventLogModuleConfig = defineConfig({ + name: 'EventLogModule', + load: (env) => ({ + backendUrl: env.required('BACKEND_URL', 'http://localhost:3344'), + secretToken: env.required( + 'BACKEND_ACCESS_TOKEN', + 'secret-backend-api-token', + ), + }), +}) diff --git a/apps/judicial-system/api/src/app/modules/event-log/eventLog.module.ts b/apps/judicial-system/api/src/app/modules/event-log/eventLog.module.ts new file mode 100644 index 000000000000..4ecbae63c524 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/event-log/eventLog.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common' + +import { EventLogResolver } from './eventLog.resolver' + +@Module({ + providers: [EventLogResolver], +}) +export class EventLogModule {} diff --git a/apps/judicial-system/api/src/app/modules/event-log/eventLog.resolver.ts b/apps/judicial-system/api/src/app/modules/event-log/eventLog.resolver.ts new file mode 100644 index 000000000000..87ad7868bff5 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/event-log/eventLog.resolver.ts @@ -0,0 +1,36 @@ +import { Inject, UseGuards } from '@nestjs/common' +import { Args, Context, Mutation, Resolver } from '@nestjs/graphql' + +import type { Logger } from '@island.is/logging' +import { LOGGER_PROVIDER } from '@island.is/logging' + +import { + CurrentGraphQlUser, + JwtGraphQlAuthGuard, +} from '@island.is/judicial-system/auth' +import type { User } from '@island.is/judicial-system/types' + +import { BackendApi } from '../../data-sources' +import { CreateEventLogInput } from '../event-log/dto/createEventLog.input' + +@UseGuards(JwtGraphQlAuthGuard) +@Resolver() +export class EventLogResolver { + constructor( + @Inject(LOGGER_PROVIDER) + private readonly logger: Logger, + ) {} + + @Mutation(() => Boolean, { nullable: true }) + async createEventLog( + @Args('input', { type: () => CreateEventLogInput }) + input: CreateEventLogInput, + @CurrentGraphQlUser() user: User, + @Context('dataSources') { backendApi }: { backendApi: BackendApi }, + ): Promise { + this.logger.debug(`Creating event log for case ${input.caseId}`) + + const res = await backendApi.createEventLog(input, user.role) + return res.ok + } +} diff --git a/apps/judicial-system/api/src/app/modules/event-log/index.ts b/apps/judicial-system/api/src/app/modules/event-log/index.ts new file mode 100644 index 000000000000..cda38e8704b3 --- /dev/null +++ b/apps/judicial-system/api/src/app/modules/event-log/index.ts @@ -0,0 +1,2 @@ +export { CreateEventLogInput } from './dto/createEventLog.input' +export { EventLog } from './models/eventLog.model' diff --git a/apps/judicial-system/api/src/app/modules/case/models/eventLog.model.ts b/apps/judicial-system/api/src/app/modules/event-log/models/eventLog.model.ts similarity index 99% rename from apps/judicial-system/api/src/app/modules/case/models/eventLog.model.ts rename to apps/judicial-system/api/src/app/modules/event-log/models/eventLog.model.ts index b848add4e8f0..b5b5061c7b5a 100644 --- a/apps/judicial-system/api/src/app/modules/case/models/eventLog.model.ts +++ b/apps/judicial-system/api/src/app/modules/event-log/models/eventLog.model.ts @@ -3,7 +3,6 @@ import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql' import { EventType, UserRole } from '@island.is/judicial-system/types' registerEnumType(EventType, { name: 'EventType' }) - @ObjectType() export class EventLog { @Field(() => ID) diff --git a/apps/judicial-system/api/src/app/modules/index.ts b/apps/judicial-system/api/src/app/modules/index.ts index b17a36b54899..9585044893c3 100644 --- a/apps/judicial-system/api/src/app/modules/index.ts +++ b/apps/judicial-system/api/src/app/modules/index.ts @@ -13,3 +13,4 @@ export { IndictmentCountModule } from './indictment-count/indictmentCount.module export { DefenderModule } from './defender/defender.module' export { CaseListModule } from './case-list/caseList.module' export { defenderModuleConfig } from './defender/defender.config' +export { EventLogModule } from './event-log/eventLog.module' diff --git a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts index 10fcb9e3d27a..30af6da23667 100644 --- a/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/event-log/eventLog.controller.ts @@ -1,23 +1,17 @@ -import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common' +import { Body, Controller, Post, UseGuards } from '@nestjs/common' import { ApiCreatedResponse } from '@nestjs/swagger' -import type { Logger } from '@island.is/logging' -import { LOGGER_PROVIDER } from '@island.is/logging' - import { TokenGuard } from '@island.is/judicial-system/auth' import { CreateEventLogDto } from './dto/createEventLog.dto' import { EventLogService } from './eventLog.service' -@Controller('api/event-log') +@Controller('api/eventLog') export class EventLogController { - constructor( - private readonly eventLogService: EventLogService, - @Inject(LOGGER_PROVIDER) private readonly logger: Logger, - ) {} + constructor(private readonly eventLogService: EventLogService) {} @UseGuards(TokenGuard) - @Post('log-event') + @Post('event') @ApiCreatedResponse({ description: 'Logs an event to event log' }) logEvent(@Body() event: CreateEventLogDto): Promise { return this.eventLogService.create(event) diff --git a/apps/judicial-system/web/messages/Core/errors.ts b/apps/judicial-system/web/messages/Core/errors.ts index 001f8288522a..806d5db20aec 100644 --- a/apps/judicial-system/web/messages/Core/errors.ts +++ b/apps/judicial-system/web/messages/Core/errors.ts @@ -117,4 +117,9 @@ export const errors = defineMessages({ description: 'Notaður sem skilaboð þegar ekki tókst að sækja gögn úr gagnagrunni', }, + createEventLog: { + id: 'judicial.system.core:errors.create_event_log', + defaultMessage: 'Upp kom villa við að skrá aðgerð', + description: 'Notaður sem villuskilaboð þegar ekki gengur að skrá atburð', + }, }) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx index 37e9619dec96..017464d3b85d 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx @@ -26,6 +26,7 @@ import { import { CaseFileCategory, CaseIndictmentRulingDecision, + EventType, ServiceRequirement, } from '@island.is/judicial-system-web/src/graphql/schema' import { @@ -33,32 +34,51 @@ import { useS3Upload, useUploadFiles, } from '@island.is/judicial-system-web/src/utils/hooks' +import useEventLog from '@island.is/judicial-system-web/src/utils/hooks/useEventLog' import strings from './Completed.strings' const Completed: FC = () => { const { formatMessage } = useIntl() - const { updateDefendantState } = useDefendants() + const { setAndSendDefendantToServer } = useDefendants() const { workingCase, setWorkingCase, isLoadingWorkingCase, caseNotFound } = useContext(FormContext) const { uploadFiles, addUploadFiles, removeUploadFile, updateUploadFile } = useUploadFiles(workingCase.caseFiles) const { handleUpload, handleRemove } = useS3Upload(workingCase.id) + const { createEventLog } = useEventLog() const [modalVisible, setModalVisible] = useState<'SENT_TO_PUBLIC_PROSECUTOR'>() + const sentToPublicProsecutor = workingCase.eventLogs?.some( + (log) => log.eventType === EventType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + ) + const handleNextButtonClick = useCallback(async () => { const allSucceeded = await handleUpload( uploadFiles.filter((file) => !file.key), updateUploadFile, ) - if (!allSucceeded) { return } + const eventLogCreated = createEventLog({ + caseId: workingCase.id, + eventType: EventType.INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR, + }) + if (!eventLogCreated) { + return + } + setModalVisible('SENT_TO_PUBLIC_PROSECUTOR') - }, [handleUpload, uploadFiles, updateUploadFile]) + }, [ + handleUpload, + uploadFiles, + updateUploadFile, + createEventLog, + workingCase.id, + ]) const handleRemoveFile = useCallback( (file: UploadFile) => { @@ -119,136 +139,145 @@ const Completed: FC = () => { - {isRulingOrFine && ( - - - - file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE, - )} - accept="application/pdf" - header={formatMessage(core.uploadBoxTitle)} - buttonLabel={formatMessage(core.uploadBoxButtonLabel)} - description={formatMessage(core.uploadBoxDescription, { - fileEndings: '.pdf', - })} - onChange={(files) => - handleCriminalRecordUpdateUpload( - files, - CaseFileCategory.CRIMINAL_RECORD_UPDATE, - ) - } - onRemove={(file) => handleRemoveFile(file)} - /> - - )} - {workingCase.indictmentRulingDecision === - CaseIndictmentRulingDecision.RULING && ( - - - {workingCase.defendants?.map((defendant, index) => ( - - - - - { - updateDefendantState( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: - ServiceRequirement.NOT_APPLICABLE, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage( - strings.serviceRequirementNotApplicable, - )} - /> - - - { - updateDefendantState( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: ServiceRequirement.REQUIRED, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage(strings.serviceRequirementRequired)} - /> - - + {isRulingOrFine && ( + + + + file.category === CaseFileCategory.CRIMINAL_RECORD_UPDATE, + )} + accept="application/pdf" + header={formatMessage(core.uploadBoxTitle)} + buttonLabel={formatMessage(core.uploadBoxButtonLabel)} + description={formatMessage(core.uploadBoxDescription, { + fileEndings: '.pdf', + })} + onChange={(files) => + handleCriminalRecordUpdateUpload( + files, + CaseFileCategory.CRIMINAL_RECORD_UPDATE, + ) + } + onRemove={(file) => handleRemoveFile(file)} + /> + + )} + {workingCase.indictmentRulingDecision === + CaseIndictmentRulingDecision.RULING && ( + + + {workingCase.defendants?.map((defendant, index) => ( + { - updateDefendantState( - { - defendantId: defendant.id, - caseId: workingCase.id, - serviceRequirement: ServiceRequirement.NOT_REQUIRED, - }, - setWorkingCase, - ) - }} - large - backgroundColor="white" - label={formatMessage(strings.serviceRequirementNotRequired)} - /> - + > + + + + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: + ServiceRequirement.NOT_APPLICABLE, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage( + strings.serviceRequirementNotApplicable, + )} + /> + + + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: ServiceRequirement.REQUIRED, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage( + strings.serviceRequirementRequired, + )} + /> + + { + setAndSendDefendantToServer( + { + defendantId: defendant.id, + caseId: workingCase.id, + serviceRequirement: + ServiceRequirement.NOT_REQUIRED, + }, + setWorkingCase, + ) + }} + large + backgroundColor="white" + label={formatMessage( + strings.serviceRequirementNotRequired, + )} + /> + + + ))} - ))} - + )} + )} { + const { formatMessage } = useIntl() + const [createEventLogMutation] = useCreateEventLogMutation() + + const createEventLog = useCallback( + async (eventLog: CreateEventLogInput) => { + try { + const { data } = await createEventLogMutation({ + variables: { + input: eventLog, + }, + }) + + return Boolean(data?.createEventLog) + } catch (error) { + toast.error(formatMessage(errors.createEventLog)) + return false + } + }, + [createEventLogMutation, formatMessage], + ) + + return { createEventLog } +} +export default useEventLog diff --git a/libs/judicial-system/types/src/lib/eventLog.ts b/libs/judicial-system/types/src/lib/eventLog.ts index 17701e298d89..1ecb2cda28b3 100644 --- a/libs/judicial-system/types/src/lib/eventLog.ts +++ b/libs/judicial-system/types/src/lib/eventLog.ts @@ -6,4 +6,5 @@ export enum EventType { LOGIN_BYPASS_UNAUTHORIZED = 'LOGIN_BYPASS_UNAUTHORIZED', INDICTMENT_CONFIRMED = 'INDICTMENT_CONFIRMED', CASE_RECEIVED_BY_COURT = 'CASE_RECEIVED_BY_COURT', + INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR = 'INDICTMENT_SENT_TO_PUBLIC_PROSECUTOR', }