diff --git a/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts b/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts new file mode 100644 index 000000000000..c4b9adffdcd7 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/case/dto/internalCases.dto.ts @@ -0,0 +1,10 @@ +import { IsNotEmpty, IsString } from 'class-validator' + +import { ApiProperty } from '@nestjs/swagger' + +export class InternalCasesDto { + @IsNotEmpty() + @IsString() + @ApiProperty({ type: String }) + readonly nationalId!: string +} 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 9e127847386f..585a3e7c04f4 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 @@ -13,6 +13,7 @@ import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' import { TokenGuard } from '@island.is/judicial-system/auth' +import { formatNationalId } from '@island.is/judicial-system/formatters' import { messageEndpoint, MessageType, @@ -25,6 +26,7 @@ import { import { CaseEvent, EventService } from '../event' import { DeliverDto } from './dto/deliver.dto' +import { InternalCasesDto } from './dto/internalCases.dto' import { InternalCreateCaseDto } from './dto/internalCreateCase.dto' import { CurrentCase } from './guards/case.decorator' import { CaseCompletedGuard } from './guards/caseCompleted.guard' @@ -68,6 +70,21 @@ export class InternalCaseController { return this.internalCaseService.archive() } + @Post('cases/indictments') + @ApiOkResponse({ + type: Case, + isArray: true, + description: 'Gets all indictment cases', + }) + getIndictmentCases( + @Body() internalCasesDto: InternalCasesDto, + ): Promise { + this.logger.debug('Getting all indictment cases') + const nationalId = formatNationalId(internalCasesDto.nationalId) + + return this.internalCaseService.getIndictmentCases(nationalId) + } + @UseGuards(CaseExistsGuard) @Post( `case/:caseId/${messageEndpoint[MessageType.DELIVERY_TO_COURT_PROSECUTOR]}`, 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 2deede784006..690b48e5e69e 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 @@ -60,6 +60,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 { DateLog } from './models/dateLog.model' import { DeliverResponse } from './models/deliver.response' import { caseModuleConfig } from './case.config' @@ -1151,4 +1152,19 @@ export class InternalCaseService { return originalAncestor } + + async getIndictmentCases(nationalId: string): Promise { + return this.caseModel.findAll({ + include: [ + { model: Defendant, as: 'defendants' }, + { model: DateLog, as: 'dateLogs' }, + ], + order: [[{ model: DateLog, as: 'dateLogs' }, 'created', 'DESC']], + attributes: ['id', 'courtCaseNumber', 'type', 'state'], + where: { + type: CaseType.INDICTMENT, + '$defendants.national_id$': nationalId, + }, + }) + } } diff --git a/apps/judicial-system/digital-mailbox-api/src/app/app.controller.ts b/apps/judicial-system/digital-mailbox-api/src/app/app.controller.ts index c5ad1d0f1c76..842481baa932 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/app.controller.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/app.controller.ts @@ -1,14 +1,13 @@ -import { Controller, Get, Inject } from '@nestjs/common' -import { UseGuards } from '@nestjs/common' +import { Controller, Get, Inject, Query, UseGuards } from '@nestjs/common' import { ApiCreatedResponse } from '@nestjs/swagger' -import type { Logger } from '@island.is/logging' -import { LOGGER_PROVIDER } from '@island.is/logging' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import type { User as TUser } from '@island.is/judicial-system/types' import { JwtAuthGuard } from './guards/auth.guard' import { User } from './guards/user.decorator' +import { CasesResponse } from './models/cases.response' import { AppService } from './app.service' @Controller('api') @@ -26,4 +25,15 @@ export class AppController { return this.appService.testConnection(user.nationalId) } + + @Get('cases') + @ApiCreatedResponse({ type: String, description: 'Get all cases' }) + async getAllCases( + @User() user: Pick, + @Query() query?: { lang: string }, + ): Promise { + this.logger.debug('Getting all cases') + + return this.appService.getCases(user.nationalId, query?.lang) + } } diff --git a/apps/judicial-system/digital-mailbox-api/src/app/app.service.ts b/apps/judicial-system/digital-mailbox-api/src/app/app.service.ts index 58fe6b0fb77e..0941a71390e4 100644 --- a/apps/judicial-system/digital-mailbox-api/src/app/app.service.ts +++ b/apps/judicial-system/digital-mailbox-api/src/app/app.service.ts @@ -1,11 +1,16 @@ -import { Inject, Injectable } from '@nestjs/common' +import { BadGatewayException, Inject, Injectable } from '@nestjs/common' -import type { Logger } from '@island.is/logging' -import { LOGGER_PROVIDER } from '@island.is/logging' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { type ConfigType } from '@island.is/nest/config' -import { AuditTrailService } from '@island.is/judicial-system/audit-trail' +import { + AuditedAction, + AuditTrailService, +} from '@island.is/judicial-system/audit-trail' +import { isCompletedCase } from '@island.is/judicial-system/types' +import { CasesResponse } from './models/cases.response' +import { InternalCasesResponse } from './models/internalCases.response' import { digitalMailboxModuleConfig } from './app.config' @Injectable() @@ -24,4 +29,76 @@ export class AppService { async testConnection(nationalId: string): Promise { return this.test(nationalId) } + + private format( + response: InternalCasesResponse[], + lang?: string, + ): CasesResponse[] { + return response.map((item: InternalCasesResponse) => { + const language = lang?.toLowerCase() + + return { + id: item.id, + state: { + color: isCompletedCase(item.state) ? 'purple' : 'blue', + label: + language === 'en' + ? isCompletedCase(item.state) + ? 'Completed' + : 'Active' + : isCompletedCase(item.state) + ? 'Lokið' + : 'Í vinnslu', + }, + caseNumber: + language === 'en' + ? `Case number ${item.courtCaseNumber}` + : `Málsnúmer ${item.courtCaseNumber}`, + type: language === 'en' ? 'Indictment' : 'Ákæra', + } + }) + } + + private async getAllCases( + nationalId: string, + lang?: string, + ): Promise { + return fetch(`${this.config.backendUrl}/api/internal/cases/indictments`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: `Bearer ${this.config.secretToken}`, + }, + body: JSON.stringify({ nationalId }), + }) + .then(async (res) => { + const response = await res.json() + + if (res.ok) { + return this.format(response, lang) + } + + if (res.status < 500) { + throw new BadGatewayException(response?.detail) + } + + throw response + }) + .catch((reason) => { + if (reason instanceof BadGatewayException) { + throw reason + } + + throw new BadGatewayException(reason) + }) + } + + async getCases(nationalId: string, lang?: string): Promise { + return this.auditTrailService.audit( + 'digital-mailbox-api', + AuditedAction.GET_INDICTMENTS, + this.getAllCases(nationalId, lang), + nationalId, + ) + } } diff --git a/apps/judicial-system/digital-mailbox-api/src/app/models/cases.response.ts b/apps/judicial-system/digital-mailbox-api/src/app/models/cases.response.ts new file mode 100644 index 000000000000..36b1db25739d --- /dev/null +++ b/apps/judicial-system/digital-mailbox-api/src/app/models/cases.response.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger' + +export class CasesResponse { + @ApiProperty({ type: String }) + id!: string + + @ApiProperty({ type: String }) + caseNumber!: string + + @ApiProperty({ type: String }) + type!: string + + @ApiProperty({ type: Object }) + state!: { + color: string + label: string + } +} diff --git a/apps/judicial-system/digital-mailbox-api/src/app/models/internalCases.response.ts b/apps/judicial-system/digital-mailbox-api/src/app/models/internalCases.response.ts new file mode 100644 index 000000000000..84b653596213 --- /dev/null +++ b/apps/judicial-system/digital-mailbox-api/src/app/models/internalCases.response.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger' + +import { CaseState } from '@island.is/judicial-system/types' + +export class InternalCasesResponse { + @ApiProperty({ type: String }) + id!: string + + @ApiProperty({ type: String }) + courtCaseNumber!: string + + @ApiProperty({ type: String }) + type!: string + + @ApiProperty({ type: String }) + state!: CaseState +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts index 76d455e44a85..8a7996ac0047 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/directorate-of-immigration/citizenship/citizenship.service.ts @@ -74,7 +74,15 @@ export class CitizenshipService extends BaseTemplateApiService { auth, }: TemplateApiModuleActionProps): Promise { const validApplicant = await this.getApplicantValidity(auth) - if (!validApplicant.applicantExists && !validApplicant.isEESCitizen) { + const validOptions = [] + + for (const [key, value] of Object.entries(validApplicant)) { + if (value) { + validOptions.push(key) + } + } + + if (validOptions.length === 0) { throw new TemplateApiError( { title: errorMessages.applicationConditionsNotMet, @@ -167,7 +175,15 @@ export class CitizenshipService extends BaseTemplateApiService { auth, }: TemplateApiModuleActionProps) { const validApplicant = await this.getApplicantValidity(auth) - if (!validApplicant.applicantExists && !validApplicant.isEESCitizen) { + const validOptions = [] + + for (const [key, value] of Object.entries(validApplicant)) { + if (value) { + validOptions.push(key) + } + } + + if (validOptions.length === 0) { throw new TemplateApiError( { title: errorMessages.applicationConditionsNotMet, diff --git a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/dataSchema.ts b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/dataSchema.ts index 55e1a3ed88f6..7895eda6905d 100644 --- a/libs/application/templates/directorate-of-immigration/citizenship/src/lib/dataSchema.ts +++ b/libs/application/templates/directorate-of-immigration/citizenship/src/lib/dataSchema.ts @@ -294,12 +294,7 @@ export const CitizenshipSchema = z.object({ passport: PassportSchema, childrenPassport: z.array(ChildrenPassportSchema).optional(), maritalStatus: MaritalStatusSchema, - // TODO revert - // formerIcelander: z - // .string() - // .min(1) - // .refine((v) => v === YES), - formerIcelander: z.enum([YES, NO]), + formerIcelander: z.enum([YES, NO]).refine((v) => v === YES), supportingDocuments: SupportingDocumentsSchema, childrenSupportingDocuments: z .array(ChildrenSupportingDocumentsSchema) diff --git a/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts b/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts index b66b063dd8e1..cfe3b161dd05 100644 --- a/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts +++ b/libs/judicial-system/audit-trail/src/lib/auditTrail.service.ts @@ -13,6 +13,7 @@ import { auditTrailModuleConfig } from './auditTrail.config' export enum AuditedAction { LOGIN = 'LOGIN', GET_CASES = 'GET_CASES', + GET_INDICTMENTS = 'GET_INDICTMENTS', GET_CASE = 'GET_CASE', CREATE_CASE = 'CREATE_CASE', UPDATE_CASE = 'UPDATE_CASE', diff --git a/libs/service-portal/education/src/screens/DrivingLessonsBook/DrivingLessonsBook.tsx b/libs/service-portal/education/src/screens/DrivingLessonsBook/DrivingLessonsBook.tsx index 0c26876718fd..0a6a258ba475 100644 --- a/libs/service-portal/education/src/screens/DrivingLessonsBook/DrivingLessonsBook.tsx +++ b/libs/service-portal/education/src/screens/DrivingLessonsBook/DrivingLessonsBook.tsx @@ -16,7 +16,6 @@ import { m, formatDate, IntroHeader, - EmptyState, FootNote, LinkResolver, SAMGONGUSTOFA_SLUG, @@ -29,6 +28,7 @@ import { import PhysicalLessons from '../../components/DrivingLessonsTables/PhysicalLessons' import DrivingLessonsSchools from '../../components/DrivingLessonsTables/DrivingLessonsSchools' import Exams from '../../components/DrivingLessonsTables/Exams' +import { Problem } from '@island.is/react-spa/shared' export const GET_STUDENT_BOOK = gql` query GetUserDrivingLessonsBook { @@ -104,6 +104,21 @@ const DrivingLessonsBook = () => { )} + {!loading && !error && !book?.createdOn && ( + + + + + + )} {book?.createdOn && !loading && ( <> @@ -198,26 +213,25 @@ const DrivingLessonsBook = () => { data={book?.testResults} /> )} + )} {!loading && !error && !book?.createdOn && ( - - - - - - - + + )} - ) }