From 9c94bef718eb6c5492032aded0cd5d0f653fa0d1 Mon Sep 17 00:00:00 2001 From: albinagu <47886428+albinagu@users.noreply.github.com> Date: Fri, 15 Nov 2024 11:27:07 +0000 Subject: [PATCH 1/6] fix(portals-admin): petition - export list (#16866) * fix(portals-admin): petition - export list * revert * f * test * test * yarn lock * revert lock * image path * Restore yarn --------- Co-authored-by: kksteini <77672665+kksteini@users.noreply.github.com> Co-authored-by: kksteini --- .../ExportList/MyPdfDocument/index.tsx | 162 ------------------ .../ExportList/MyPdfDocument/island.png | Bin 6368 -> 0 bytes .../ExportList/MyPdfDocument/thjodskra.png | Bin 7465 -> 0 bytes .../src/components/ExportList/downloadCSV.ts | 37 ---- .../components/ExportList/exportList.graphql | 5 + .../src/components/ExportList/index.tsx | 83 +++++---- .../src/components/ExportList/styles.css.ts | 6 + .../src/components/ListSignersTable/index.tsx | 8 +- .../PetitionsTable/ExportPetition/index.tsx | 2 - .../src/screens/PetitionsTable/index.tsx | 6 +- 10 files changed, 66 insertions(+), 243 deletions(-) delete mode 100644 libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/index.tsx delete mode 100644 libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/island.png delete mode 100644 libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/thjodskra.png delete mode 100644 libs/portals/admin/petition/src/components/ExportList/downloadCSV.ts create mode 100644 libs/portals/admin/petition/src/components/ExportList/exportList.graphql diff --git a/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/index.tsx b/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/index.tsx deleted file mode 100644 index b7885ba5aa48..000000000000 --- a/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/index.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { - Page, - Text, - Document, - Image, - View, - Font, - StyleSheet, -} from '@react-pdf/renderer' -import { - Endorsement, - EndorsementList, - PaginatedEndorsementResponse, -} from '@island.is/api/schema' -import { dark200 } from '@island.is/island-ui/theme' -import { formatDate } from '../../../lib/utils/utils' -import { format as formatNationalId } from 'kennitala' -import island from './island.png' -import thjodskra from './thjodskra.png' - -const MyPdfDocument = (data: { - petition?: EndorsementList - petitionSigners: PaginatedEndorsementResponse -}) => { - const { petition, petitionSigners } = data - return ( - - - {/* Header */} - - - {/* Body */} - - Upplýsingar um undirskriftalista - Heiti undirskriftalista - {petition?.title} - Um undirskriftalista - {petition?.description} - - - Opinn til: - {formatDate(petition?.closedDate)} - - - Fjöldi undirskrifta: - {petitionSigners?.totalCount} - - - - - Ábyrgðarmaður: - {petition?.ownerName} - - - Kennitala ábyrgðarmanns: - {formatNationalId(petition?.owner as string)} - - - - - - Dags. skráð - Nafn - Sveitarfélag - - - {petitionSigners?.data?.map((sign: Endorsement) => { - return ( - - - {formatDate(sign.created)} - - - {sign.meta.fullName ? sign.meta.fullName : ''} - - - {sign.meta.locality ? sign.meta.locality : ''} - - - ) - })} - - - - {/* Footer */} - - - - ) -} - -export const pdfStyles = StyleSheet.create({ - body: { - paddingTop: 40, - paddingBottom: 70, - paddingHorizontal: 60, - fontFamily: 'IBM Plex Sans', - fontSize: 10, - }, - title: { - fontSize: 24, - }, - header: { - fontSize: 14, - fontWeight: 600, - marginTop: 20, - marginBottom: 5, - }, - tableHeaderDate: { - fontWeight: 600, - marginBottom: 5, - width: '17%', - }, - tableHeader: { - fontWeight: 600, - marginBottom: 5, - width: '33%', - }, - row: { - flexDirection: 'row', - }, - widthHalf: { - width: '50%', - }, - listInfo: { - paddingBottom: 30, - }, - tableView: { - paddingTop: 25, - borderTop: `1px solid ${dark200}`, - }, - tableRow: { - flexDirection: 'row', - marginVertical: 5, - }, - image: { - width: 120, - marginBottom: 20, - marginLeft: -9, - }, - footerImage: { - position: 'absolute', - left: 60, - bottom: 40, - width: 120, - }, -}) - -Font.register({ - family: 'IBM Plex Sans', - fonts: [ - { - src: './assets/fonts/ibm-plex-sans-v7-latin-regular.ttf', - }, - { - src: './assets/fonts/ibm-plex-sans-v7-latin-600.ttf', - fontWeight: 600, - }, - ], -}) - -export default MyPdfDocument diff --git a/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/island.png b/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/island.png deleted file mode 100644 index c67725fe2eb90ab79e7f50007e154465e310801f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6368 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 Y5JV|*XAWuiKNmQfP#x7Oh;``y0Ee6nPyhe` diff --git a/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/thjodskra.png b/libs/portals/admin/petition/src/components/ExportList/MyPdfDocument/thjodskra.png deleted file mode 100644 index 96058703b270a392060ac3ccff445232c7434785..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7465 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 diff --git a/libs/portals/admin/petition/src/components/ExportList/downloadCSV.ts b/libs/portals/admin/petition/src/components/ExportList/downloadCSV.ts deleted file mode 100644 index 13f4ee0c5a82..000000000000 --- a/libs/portals/admin/petition/src/components/ExportList/downloadCSV.ts +++ /dev/null @@ -1,37 +0,0 @@ -import CSVStringify from 'csv-stringify' -import { formatDate } from '../../lib/utils/utils' -import { - Endorsement, - PaginatedEndorsementResponse, -} from '@island.is/api/schema' - -export const getCSV = async ( - signers: PaginatedEndorsementResponse, - fileName: string, -) => { - const name = `${fileName}` - const dataArray = signers.data.map((item: Endorsement) => [ - formatDate(item.created) ?? '', - item.meta.fullName ?? '', - item.meta.locality ?? '', - ]) - - await downloadCSV(name, ['Dagsetning', 'Nafn', 'Sveitarfélag'], dataArray) -} - -export const downloadCSV = async ( - name: string, - header: string[], - data: Array>, -) => { - const csvData = [header, ...data] - CSVStringify(csvData, (err, output) => { - const encodedUri = encodeURI(`data:text/csv;charset=utf-8,${output}`) - const link = document.createElement('a') - link.setAttribute('href', encodedUri) - link.setAttribute('download', name) - document.body.appendChild(link) - - link.click() - }) -} diff --git a/libs/portals/admin/petition/src/components/ExportList/exportList.graphql b/libs/portals/admin/petition/src/components/ExportList/exportList.graphql new file mode 100644 index 000000000000..9a431559801b --- /dev/null +++ b/libs/portals/admin/petition/src/components/ExportList/exportList.graphql @@ -0,0 +1,5 @@ +mutation ExportList($input: ExportEndorsementListInput!) { + endorsementSystemExportList(input: $input) { + url + } +} diff --git a/libs/portals/admin/petition/src/components/ExportList/index.tsx b/libs/portals/admin/petition/src/components/ExportList/index.tsx index 0bc7c06b4b8a..738f58baf43b 100644 --- a/libs/portals/admin/petition/src/components/ExportList/index.tsx +++ b/libs/portals/admin/petition/src/components/ExportList/index.tsx @@ -1,20 +1,11 @@ import { FC, ReactElement } from 'react' -import { Box, DropdownMenu } from '@island.is/island-ui/core' +import { Box, DropdownMenu, LoadingDots } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' import * as styles from './styles.css' import { m } from '../../lib/messages' -import { - EndorsementList, - PaginatedEndorsementResponse, -} from '@island.is/api/schema' -import MyPdfDocument from './MyPdfDocument' -import { usePDF } from '@react-pdf/renderer' - +import { useExportListMutation } from './exportList.generated' interface Props { - petition: EndorsementList - petitionSigners: PaginatedEndorsementResponse petitionId: string - onGetCSV: () => void dropdownItems?: { href?: string onClick?: () => void @@ -28,43 +19,75 @@ interface Props { } export const ExportList: FC> = ({ - petition, - petitionSigners, petitionId, - onGetCSV, dropdownItems = [], }) => { const { formatMessage } = useLocale() - const [document] = usePDF({ - document: ( - - ), + const [exportListPdf, { loading: loadingPdf }] = useExportListMutation({ + variables: { + input: { + listId: petitionId, + fileType: 'pdf', + }, + }, + onCompleted: (data) => { + window.open(data.endorsementSystemExportList.url, '_blank') + }, + }) + + const [exportListCsv, { loading: loadingCsv }] = useExportListMutation({ + variables: { + input: { + listId: petitionId, + fileType: 'csv', + }, + }, + onCompleted: (data) => { + window.open(data.endorsementSystemExportList.url, '_blank') + }, }) return ( ( - - {formatMessage(m.asPdf)} - - ), + render: () => + loadingPdf ? ( + + + + ) : ( + exportListPdf()} + > + {formatMessage(m.asPdf)} + + ), }, { - onClick: () => onGetCSV(), title: formatMessage(m.asCsv), + render: () => + loadingCsv ? ( + + + + ) : ( + exportListCsv()} + > + {formatMessage(m.asCsv)} + + ), }, ...dropdownItems, ]} diff --git a/libs/portals/admin/petition/src/components/ExportList/styles.css.ts b/libs/portals/admin/petition/src/components/ExportList/styles.css.ts index f0998466e1f4..9f659ad2b7dc 100644 --- a/libs/portals/admin/petition/src/components/ExportList/styles.css.ts +++ b/libs/portals/admin/petition/src/components/ExportList/styles.css.ts @@ -7,6 +7,7 @@ export const buttonWrapper = style({ export const menuItem = style({ paddingTop: '16px', + paddingBottom: '16px', display: 'flex', justifyContent: 'center', transition: 'color .2s', @@ -18,4 +19,9 @@ export const menuItem = style({ color: theme.color.blue400, }, }, + '@media': { + [`screen and (max-width: ${theme.breakpoints.sm}px)`]: { + fontSize: 12, + }, + }, }) diff --git a/libs/portals/admin/petition/src/components/ListSignersTable/index.tsx b/libs/portals/admin/petition/src/components/ListSignersTable/index.tsx index 7b9fce1629bc..984b4d91ace1 100644 --- a/libs/portals/admin/petition/src/components/ListSignersTable/index.tsx +++ b/libs/portals/admin/petition/src/components/ListSignersTable/index.tsx @@ -15,7 +15,6 @@ import { EndorsementList, PaginatedEndorsementResponse, } from '@island.is/api/schema' -import { getCSV } from '../ExportList/downloadCSV' const ListSignersTable = (data: { listId: string @@ -44,12 +43,7 @@ const ListSignersTable = (data: { {formatMessage(m.petitionsOverview)} - getCSV(data.petitionSigners, 'Undirskriftalisti')} - /> + diff --git a/libs/portals/my-pages/petitions/src/screens/PetitionsTable/ExportPetition/index.tsx b/libs/portals/my-pages/petitions/src/screens/PetitionsTable/ExportPetition/index.tsx index f8b85267e442..c906594ad194 100644 --- a/libs/portals/my-pages/petitions/src/screens/PetitionsTable/ExportPetition/index.tsx +++ b/libs/portals/my-pages/petitions/src/screens/PetitionsTable/ExportPetition/index.tsx @@ -10,12 +10,10 @@ import * as styles from '../styles.css' import { m } from '../../../lib/messages' import copyToClipboard from 'copy-to-clipboard' import { toast } from 'react-toastify' -import { EndorsementList } from '@island.is/api/schema' import { useMutation } from '@apollo/client' import { ExportList } from '../../queries' interface Props { - petition?: EndorsementList petitionId: string dropdownItems?: { href?: string diff --git a/libs/portals/my-pages/petitions/src/screens/PetitionsTable/index.tsx b/libs/portals/my-pages/petitions/src/screens/PetitionsTable/index.tsx index fd4fabdd3ac6..4105e4a83be1 100644 --- a/libs/portals/my-pages/petitions/src/screens/PetitionsTable/index.tsx +++ b/libs/portals/my-pages/petitions/src/screens/PetitionsTable/index.tsx @@ -37,11 +37,7 @@ const PetitionsTable = (data: { {formatMessage(m.petitionsOverview)} - - {data.canEdit && ( - - )} - + {data.canEdit && } From e783a7535e519a256f63300364c80d031209e665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 15 Nov 2024 12:23:03 +0000 Subject: [PATCH 2/6] fix(j-s): Service Status Event (#16892) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../backend/src/app/modules/subpoena/subpoena.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 9f7322f11fe3..8ecf5820db5e 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 @@ -241,7 +241,11 @@ export class SubpoenaService { defenderNationalId, ) - if (update.serviceStatus && subpoena.case) { + if ( + update.serviceStatus && + update.serviceStatus !== subpoena.serviceStatus && + subpoena.case + ) { this.eventService.postEvent( 'SUBPOENA_SERVICE_STATUS', subpoena.case, From fda8787d4bf4dfbc8e00896b7ba17e33ca2b56df Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:57:34 +0000 Subject: [PATCH 3/6] chore(auth-apis): Add api mocks to tests. (#16867) Co-authored-by: Valur Einarsson Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../test/me-delegations.access-incoming.spec.ts | 15 +++++++++++++++ .../mocks/companyRegistryClientService.mock.ts | 8 ++++++++ apps/services/auth/public-api/test/mocks/index.ts | 1 + apps/services/auth/public-api/test/setup.ts | 4 ++++ 4 files changed, 28 insertions(+) create mode 100644 apps/services/auth/public-api/test/mocks/companyRegistryClientService.mock.ts diff --git a/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts b/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts index 81eea480ae5b..6f5c526d1188 100644 --- a/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts +++ b/apps/services/auth/delegation-api/src/app/delegations/test/me-delegations.access-incoming.spec.ts @@ -9,7 +9,9 @@ import { DelegationScope, DelegationsIndexService, } from '@island.is/auth-api-lib' +import { RskRelationshipsClient } from '@island.is/clients-rsk-relationships' import { NationalRegistryClientService } from '@island.is/clients/national-registry-v2' +import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry' import { expectMatchingDelegations, FixtureFactory, @@ -41,9 +43,16 @@ describe('MeDelegationsController', () => { }) server = request(app.getHttpServer()) delegationIndexService = app.get(DelegationsIndexService) + const rskRelationshipsClientService = app.get(RskRelationshipsClient) const nationalRegistryClientService = app.get( NationalRegistryClientService, ) + const companyRegistryClientService = app.get( + CompanyRegistryClientService, + ) + jest + .spyOn(nationalRegistryClientService, 'getCustodyChildren') + .mockImplementation(async () => []) jest .spyOn(nationalRegistryClientService, 'getIndividual') .mockImplementation(async (nationalId: string) => @@ -52,6 +61,12 @@ describe('MeDelegationsController', () => { name: fromName, }), ) + jest + .spyOn(rskRelationshipsClientService, 'getIndividualRelationships') + .mockImplementation(async () => null) + jest + .spyOn(companyRegistryClientService, 'getCompany') + .mockImplementation(async () => null) jest .spyOn(delegationIndexService, 'indexDelegations') .mockImplementation() diff --git a/apps/services/auth/public-api/test/mocks/companyRegistryClientService.mock.ts b/apps/services/auth/public-api/test/mocks/companyRegistryClientService.mock.ts new file mode 100644 index 000000000000..fec204a16425 --- /dev/null +++ b/apps/services/auth/public-api/test/mocks/companyRegistryClientService.mock.ts @@ -0,0 +1,8 @@ +import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry' + +export const CompanyRegistryClientServiceMock: Partial = + { + getCompany() { + return Promise.resolve(null) + }, + } diff --git a/apps/services/auth/public-api/test/mocks/index.ts b/apps/services/auth/public-api/test/mocks/index.ts index 77ff6e030811..b56b101b6fd9 100644 --- a/apps/services/auth/public-api/test/mocks/index.ts +++ b/apps/services/auth/public-api/test/mocks/index.ts @@ -1,3 +1,4 @@ export * from './einstaklingurApi.mock' export * from './rskProcuringClient.mock' export * from './featureFlagService.mock' +export * from './companyRegistryClientService.mock' diff --git a/apps/services/auth/public-api/test/setup.ts b/apps/services/auth/public-api/test/setup.ts index eb533a696e10..e8eef786b36e 100644 --- a/apps/services/auth/public-api/test/setup.ts +++ b/apps/services/auth/public-api/test/setup.ts @@ -16,6 +16,7 @@ import { IndividualDto, NationalRegistryClientService, } from '@island.is/clients/national-registry-v2' +import { CompanyRegistryClientService } from '@island.is/clients/rsk/company-registry' import { ConfigType } from '@island.is/nest/config' import { FeatureFlagService } from '@island.is/nest/feature-flags' import { @@ -35,6 +36,7 @@ import { import { AppModule } from '../src/app/app.module' import { + CompanyRegistryClientServiceMock, createMockEinstaklingurApi, FeatureFlagServiceMock, RskProcuringClientMock, @@ -156,6 +158,8 @@ export const setupWithAuth = async ({ .useValue(createMockEinstaklingurApi(nationalRegistryUser)) .overrideProvider(RskRelationshipsClient) .useValue(RskProcuringClientMock) + .overrideProvider(CompanyRegistryClientService) + .useValue(CompanyRegistryClientServiceMock) .overrideProvider(DelegationConfig.KEY) .useValue(delegationConfig) .overrideProvider(FeatureFlagService) From 17ef24071c0ec62349b08dad5c06929f81b6b60e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 15 Nov 2024 14:09:27 +0000 Subject: [PATCH 4/6] feat(j-s): Defender Updated Notification (#16801) * Adss a defendant national id * Removes unused imports * Stops updating defendants via API in indictment cases on court case number assignment as these updates are sent to the robot * Adds a guard to get a defender by national id * Rewrites update defendant by national id * Refactors defendant update messages * Restricts defendant restricted updates to indictment cases * Moves defendand defender choice updates and notifications to defendant classes * Update unit tests and fixxes some issues * Fixes email sending trigger and guard --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.controller.ts | 4 - .../src/app/modules/case/case.service.ts | 7 +- .../app/modules/case/internalCase.service.ts | 2 +- .../caseController/createCourtCase.spec.ts | 5 - .../case/test/caseController/update.spec.ts | 17 -- .../internalCaseController/archive.spec.ts | 4 +- .../modules/defendant/defendant.service.ts | 216 ++++++++++-------- .../guards/defendantNationalIdExists.guard.ts | 49 ++++ .../defendantNationalIdExistsGuard.spec.ts | 125 ++++++++++ .../defendant/internalDefendant.controller.ts | 18 +- .../test/defendantController/update.spec.ts | 39 +--- .../deliverDefendantToCourtGuards.spec.ts | 19 ++ .../updateDefendant.spec.ts | 77 +++++++ .../updateDefendantGuards.spec.ts | 26 +++ .../defendantNotification.service.ts | 124 ++++++---- .../defendantNotification.strings.ts | 13 ++ .../internalNotification.controller.ts | 2 +- .../subpoenaNotification.service.ts | 35 +-- .../subpoenaNotification.strings.ts | 13 -- .../subpoena/dto/updateSubpoena.dto.ts | 5 - .../subpoena/internalSubpoena.controller.ts | 2 +- .../modules/subpoena/subpoena.controller.ts | 1 - .../app/modules/subpoena/subpoena.module.ts | 3 +- .../app/modules/subpoena/subpoena.service.ts | 102 ++++----- .../test/createTestingSubpoenaModule.ts | 4 +- .../types/src/lib/notification.ts | 4 +- 26 files changed, 599 insertions(+), 317 deletions(-) create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/guards/defendantNationalIdExists.guard.ts create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/guards/test/defendantNationalIdExistsGuard.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/deliverDefendantToCourtGuards.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendant.spec.ts create mode 100644 apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendantGuards.spec.ts 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 cbab6e505062..63b82483e3a9 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 @@ -39,14 +39,10 @@ import { } from '@island.is/judicial-system/formatters' import type { User } from '@island.is/judicial-system/types' import { - CaseAppealRulingDecision, - CaseDecision, CaseState, - CaseTransition, CaseType, indictmentCases, investigationCases, - isRestrictionCase, restrictionCases, UserRole, } from '@island.is/judicial-system/types' 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 6f77a6752c3e..ae7e7b739366 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 @@ -784,10 +784,9 @@ export class CaseService { elementId: caseFile.id, })) ?? [] - const messages = this.getDeliverProsecutorToCourtMessages(theCase, user) - .concat(this.getDeliverDefendantToCourtMessages(theCase, user)) - .concat(deliverCaseFilesRecordToCourtMessages) - .concat(deliverCaseFileToCourtMessages) + const messages: Message[] = deliverCaseFilesRecordToCourtMessages.concat( + deliverCaseFileToCourtMessages, + ) if (isTrafficViolationCase(theCase)) { messages.push({ 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 211931752bfc..94b5abaa4750 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 @@ -418,7 +418,7 @@ export class InternalCaseService { collectEncryptionProperties(defendantEncryptionProperties, defendant) defendantsArchive.push(defendantArchive) - await this.defendantService.updateForArcive( + await this.defendantService.updateDatabaseDefendant( theCase.id, defendant.id, clearedDefendantProperties, diff --git a/apps/judicial-system/backend/src/app/modules/case/test/caseController/createCourtCase.spec.ts b/apps/judicial-system/backend/src/app/modules/case/test/caseController/createCourtCase.spec.ts index 318a3420af0d..90c78356e06a 100644 --- a/apps/judicial-system/backend/src/app/modules/case/test/caseController/createCourtCase.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/case/test/caseController/createCourtCase.spec.ts @@ -283,11 +283,6 @@ describe('CaseController - Create court case', () => { it('should post to queue', () => { expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ - { - type: MessageType.DELIVERY_TO_COURT_PROSECUTOR, - user, - caseId: theCase.id, - }, { type: MessageType.DELIVERY_TO_COURT_CASE_FILES_RECORD, user, 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 088190ba98dd..b3b0bb816313 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 @@ -458,23 +458,6 @@ describe('CaseController - Update', () => { it('should post to queue', () => { expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ - { - type: MessageType.DELIVERY_TO_COURT_PROSECUTOR, - user, - caseId, - }, - { - type: MessageType.DELIVERY_TO_COURT_DEFENDANT, - user, - caseId, - elementId: defendantId1, - }, - { - type: MessageType.DELIVERY_TO_COURT_DEFENDANT, - user, - caseId, - elementId: defendantId2, - }, { type: MessageType.DELIVERY_TO_COURT_CASE_FILES_RECORD, user, 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 d6beb070a348..7f06a822d4aa 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 @@ -287,13 +287,13 @@ describe('InternalCaseController - Archive', () => { ], where: archiveFilter, }) - expect(mockDefendantService.updateForArcive).toHaveBeenCalledWith( + expect(mockDefendantService.updateDatabaseDefendant).toHaveBeenCalledWith( caseId, defendantId1, { nationalId: '', name: '', address: '' }, transaction, ) - expect(mockDefendantService.updateForArcive).toHaveBeenCalledWith( + expect(mockDefendantService.updateDatabaseDefendant).toHaveBeenCalledWith( caseId, defendantId2, { nationalId: '', name: '', address: '' }, diff --git a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts index 38374e35a08b..8906b2815629 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/defendant.service.ts @@ -90,62 +90,72 @@ export class DefendantService { return defendants[0] } - private async sendUpdateDefendantMessages( + private async sendRequestCaseUpdateDefendantMessages( theCase: Case, updatedDefendant: Defendant, oldDefendant: Defendant, user: User, - ) { + ): Promise { + if (!theCase.courtCaseNumber) { + return + } + + const messages: Message[] = [] + // Handling of updates sent to the court system - if (theCase.courtCaseNumber) { - // A defendant is updated after the case has been received by the court. - if (updatedDefendant.noNationalId !== oldDefendant.noNationalId) { - // This should only happen to non-indictment cases. - // A defendant nationalId is added or removed. Attempt to add the defendant to the court case. - // In case there is no national id, the court will be notified. - await this.messageService.sendMessagesToQueue([ - this.getMessageForDeliverDefendantToCourt(updatedDefendant, user), - ]) - } else if (updatedDefendant.nationalId !== oldDefendant.nationalId) { - // This should only happen to non-indictment cases. - // A defendant is replaced. Attempt to add the defendant to the court case, - // but also ask the court to verify defendants. - await this.messageService.sendMessagesToQueue([ - this.getMessageForSendDefendantsNotUpdatedAtCourtNotification( - theCase, - user, - ), - this.getMessageForDeliverDefendantToCourt(updatedDefendant, user), - ]) - } else if ( - updatedDefendant.defenderEmail !== oldDefendant.defenderEmail - ) { - // This should only happen to indictment cases. - // A defendant's defender email is updated. - // Attempt to update the defendant in the court case. - await this.messageService.sendMessagesToQueue([ - this.getMessageForDeliverDefendantToCourt(updatedDefendant, user), - ]) - } + // A defendant is updated after the case has been received by the court. + if (updatedDefendant.noNationalId !== oldDefendant.noNationalId) { + // A defendant nationalId is added or removed. Attempt to add the defendant to the court case. + // In case there is no national id, the court will be notified. + messages.push( + this.getMessageForDeliverDefendantToCourt(updatedDefendant, user), + ) + } else if (updatedDefendant.nationalId !== oldDefendant.nationalId) { + // A defendant is replaced. Attempt to add the defendant to the court case, + // but also ask the court to verify defendants. + messages.push( + this.getMessageForSendDefendantsNotUpdatedAtCourtNotification( + theCase, + user, + ), + this.getMessageForDeliverDefendantToCourt(updatedDefendant, user), + ) } - // Handling of messages sent to participants for indictment cases - if (isIndictmentCase(theCase.type)) { - // Defender was just confirmed by judge + if (messages.length === 0) { + return + } + + return this.messageService.sendMessagesToQueue(messages) + } + + private async sendIndictmentCaseUpdateDefendantMessages( + theCase: Case, + updatedDefendant: Defendant, + oldDefendant: Defendant, + ): Promise { + if (!theCase.courtCaseNumber) { + return + } + + if (updatedDefendant.isDefenderChoiceConfirmed) { if ( - updatedDefendant.isDefenderChoiceConfirmed && - !oldDefendant.isDefenderChoiceConfirmed && - (updatedDefendant.defenderChoice === DefenderChoice.CHOOSE || - updatedDefendant.defenderChoice === DefenderChoice.DELEGATE) + updatedDefendant.defenderChoice === DefenderChoice.CHOOSE || + updatedDefendant.defenderChoice === DefenderChoice.DELEGATE ) { - await this.messageService.sendMessagesToQueue([ - { - type: MessageType.DEFENDANT_NOTIFICATION, - caseId: theCase.id, - body: { type: DefendantNotificationType.DEFENDER_ASSIGNED }, - elementId: updatedDefendant.id, - }, - ]) + // TODO: Update defender with robot if needed + + // Defender was just confirmed by judge + if (!oldDefendant.isDefenderChoiceConfirmed) { + await this.messageService.sendMessagesToQueue([ + { + type: MessageType.DEFENDANT_NOTIFICATION, + caseId: theCase.id, + body: { type: DefendantNotificationType.DEFENDER_ASSIGNED }, + elementId: updatedDefendant.id, + }, + ]) + } } } } @@ -183,19 +193,15 @@ export class DefendantService { return defendant } - async updateForArcive( + async updateDatabaseDefendant( caseId: string, defendantId: string, update: UpdateDefendantDto, - transaction: Transaction, - ): Promise { + transaction?: Transaction, + ) { const [numberOfAffectedRows, defendants] = await this.defendantModel.update( update, - { - where: { id: defendantId, caseId }, - returning: true, - transaction, - }, + { where: { id: defendantId, caseId }, returning: true, transaction }, ) return this.getUpdatedDefendant( @@ -206,69 +212,99 @@ export class DefendantService { ) } - async update( + async updateRequestCase( theCase: Case, defendant: Defendant, update: UpdateDefendantDto, user: User, ): Promise { - const [numberOfAffectedRows, defendants] = await this.defendantModel.update( + const updatedDefendant = await this.updateDatabaseDefendant( + theCase.id, + defendant.id, update, - { - where: { id: defendant.id, caseId: theCase.id }, - returning: true, - }, ) - const updatedDefendant = this.getUpdatedDefendant( - numberOfAffectedRows, - defendants, - defendant.id, + await this.sendRequestCaseUpdateDefendantMessages( + theCase, + updatedDefendant, + defendant, + user, + ) + + return updatedDefendant + } + + async updateIndictmentCase( + theCase: Case, + defendant: Defendant, + update: UpdateDefendantDto, + ): Promise { + const updatedDefendant = await this.updateDatabaseDefendant( theCase.id, + defendant.id, + update, ) - await this.sendUpdateDefendantMessages( + await this.sendIndictmentCaseUpdateDefendantMessages( theCase, updatedDefendant, defendant, - user, ) return updatedDefendant } - async updateByNationalId( - caseId: string, - defendantNationalId: string, + async update( + theCase: Case, + defendant: Defendant, + update: UpdateDefendantDto, + user: User, + ): Promise { + if (isIndictmentCase(theCase.type)) { + return this.updateIndictmentCase(theCase, defendant, update) + } else { + return this.updateRequestCase(theCase, defendant, update, user) + } + } + + async updateRestricted( + theCase: Case, + defendant: Defendant, update: InternalUpdateDefendantDto, + isDefenderChoiceConfirmed = false, + transaction?: Transaction, ): Promise { // The reason we have a separate dto for this is because requests that end here // are initiated by outside API's which should not be able to edit other fields // Defendant updated originating from the judicial system should use the UpdateDefendantDto // and go through the update method above using the defendantId. - // This is also why we set the isDefenderChoiceConfirmed to false here - the judge needs to confirm all changes. - update = { - ...update, - isDefenderChoiceConfirmed: false, - } as UpdateDefendantDto + // This is also why we may set the isDefenderChoiceConfirmed to false here - the judge needs to confirm all changes. - const [numberOfAffectedRows, defendants] = await this.defendantModel.update( - update, - { - where: { - caseId, - national_id: normalizeAndFormatNationalId(defendantNationalId), - }, - returning: true, - }, + const updatedDefendant = await this.updateDatabaseDefendant( + theCase.id, + defendant.id, + { ...update, isDefenderChoiceConfirmed }, + transaction, ) - const updatedDefendant = this.getUpdatedDefendant( - numberOfAffectedRows, - defendants, - defendants[0].id, - caseId, - ) + // Notify the court if the defendant has changed the defender choice + if ( + !updatedDefendant.isDefenderChoiceConfirmed && + updatedDefendant.defenderChoice === DefenderChoice.CHOOSE && + (updatedDefendant.defenderChoice !== defendant.defenderChoice || + updatedDefendant.defenderNationalId !== defendant.defenderNationalId) + ) { + await this.messageService.sendMessagesToQueue([ + { + type: MessageType.DEFENDANT_NOTIFICATION, + caseId: theCase.id, + elementId: updatedDefendant.id, + body: { + type: DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER, + }, + }, + ]) + } return updatedDefendant } diff --git a/apps/judicial-system/backend/src/app/modules/defendant/guards/defendantNationalIdExists.guard.ts b/apps/judicial-system/backend/src/app/modules/defendant/guards/defendantNationalIdExists.guard.ts new file mode 100644 index 000000000000..b400bccebd5e --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/guards/defendantNationalIdExists.guard.ts @@ -0,0 +1,49 @@ +import { + BadRequestException, + CanActivate, + ExecutionContext, + Injectable, + NotFoundException, +} from '@nestjs/common' + +import { normalizeAndFormatNationalId } from '@island.is/judicial-system/formatters' + +import { Case } from '../../case' + +@Injectable() +export class DefendantNationalIdExistsGuard implements CanActivate { + canActivate(context: ExecutionContext): boolean { + const request = context.switchToHttp().getRequest() + + const theCase: Case = request.case + + if (!theCase) { + throw new BadRequestException('Missing case') + } + + const defendantNationalId = request.params.defendantNationalId + + if (!defendantNationalId) { + throw new BadRequestException('Missing defendant national id') + } + + const normalizedAndFormatedNationalId = + normalizeAndFormatNationalId(defendantNationalId) + + const defendant = theCase.defendants?.find( + (defendant) => + defendant.nationalId && + normalizedAndFormatedNationalId.includes(defendant.nationalId), + ) + + if (!defendant) { + throw new NotFoundException( + `Defendant with given national id of case ${theCase.id} does not exist`, + ) + } + + request.defendant = defendant + + return true + } +} diff --git a/apps/judicial-system/backend/src/app/modules/defendant/guards/test/defendantNationalIdExistsGuard.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/guards/test/defendantNationalIdExistsGuard.spec.ts new file mode 100644 index 000000000000..07ae3267b24d --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/guards/test/defendantNationalIdExistsGuard.spec.ts @@ -0,0 +1,125 @@ +import { uuid } from 'uuidv4' + +import { + BadRequestException, + ExecutionContext, + NotFoundException, +} from '@nestjs/common' + +import { DefendantNationalIdExistsGuard } from '../defendantNationalIdExists.guard' + +interface Then { + result: boolean + error: Error +} + +type GivenWhenThen = () => Promise + +describe('Defendant National Id Exists Guard', () => { + const mockRequest = jest.fn() + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + givenWhenThen = async (): Promise => { + const guard = new DefendantNationalIdExistsGuard() + const then = {} as Then + + try { + then.result = guard.canActivate({ + switchToHttp: () => ({ getRequest: mockRequest }), + } as unknown as ExecutionContext) + } catch (error) { + then.error = error as Error + } + + return then + } + }) + + describe('defendant exists', () => { + const caseId = uuid() + const defendantId = uuid() + const defendantNationalId = uuid() + const defendant = { + id: defendantId, + nationalId: defendantNationalId, + caseId, + } + const theCase = { id: caseId, defendants: [defendant] } + const request = { + params: { caseId, defendantNationalId }, + case: theCase, + defendant: undefined, + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce(request) + + then = await givenWhenThen() + }) + + it('should activate', () => { + expect(then.result).toBe(true) + expect(request.defendant).toBe(defendant) + }) + }) + + describe('defendant does not exist', () => { + const caseId = uuid() + const defendantId = uuid() + const theCase = { + id: caseId, + defendants: [{ id: defendantId, nationalId: uuid(), caseId }], + } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ + params: { caseId, defendantNationalId: uuid() }, + case: theCase, + }) + + then = await givenWhenThen() + }) + + it('should throw NotFoundException', () => { + expect(then.error).toBeInstanceOf(NotFoundException) + expect(then.error.message).toBe( + `Defendant with given national id of case ${caseId} does not exist`, + ) + }) + }) + + describe('missing case', () => { + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: {} }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing case') + }) + }) + + describe('missing defendant id', () => { + const caseId = uuid() + const theCase = { id: caseId, defendants: [] } + let then: Then + + beforeEach(async () => { + mockRequest.mockReturnValueOnce({ params: { caseId }, case: theCase }) + + then = await givenWhenThen() + }) + + it('should throw BadRequestException', () => { + expect(then.error).toBeInstanceOf(BadRequestException) + expect(then.error.message).toBe('Missing defendant national id') + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts b/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts index f7dc724065a6..f835ad07b5b1 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/internalDefendant.controller.ts @@ -17,12 +17,14 @@ import { messageEndpoint, MessageType, } from '@island.is/judicial-system/message' +import { indictmentCases } from '@island.is/judicial-system/types' -import { Case, CaseExistsGuard, CurrentCase } from '../case' +import { Case, CaseExistsGuard, CaseTypeGuard, CurrentCase } from '../case' import { DeliverDefendantToCourtDto } from './dto/deliverDefendantToCourt.dto' import { InternalUpdateDefendantDto } from './dto/internalUpdateDefendant.dto' import { CurrentDefendant } from './guards/defendant.decorator' import { DefendantExistsGuard } from './guards/defendantExists.guard' +import { DefendantNationalIdExistsGuard } from './guards/defendantNationalIdExists.guard' import { Defendant } from './models/defendant.model' import { DeliverResponse } from './models/deliver.response' import { DefendantService } from './defendant.service' @@ -62,25 +64,25 @@ export class InternalDefendantController { ) } + @UseGuards(new CaseTypeGuard(indictmentCases), DefendantNationalIdExistsGuard) @Patch('defense/:defendantNationalId') @ApiOkResponse({ type: Defendant, description: 'Updates defendant information by case and national id', }) - async updateDefendant( + updateDefendant( @Param('caseId') caseId: string, - @Param('defendantNationalId') defendantNationalId: string, + @Param('defendantNationalId') _: string, @CurrentCase() theCase: Case, + @CurrentDefendant() defendant: Defendant, @Body() updatedDefendantChoice: InternalUpdateDefendantDto, ): Promise { this.logger.debug(`Updating defendant info for ${caseId}`) - const updatedDefendant = await this.defendantService.updateByNationalId( - theCase.id, - defendantNationalId, + return this.defendantService.updateRestricted( + theCase, + defendant, updatedDefendantChoice, ) - - return updatedDefendant } } diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/update.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/update.spec.ts index 4ead03bc4b3d..d445408d802a 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/update.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/defendantController/update.spec.ts @@ -22,6 +22,7 @@ interface Then { type GivenWhenThen = ( defendantUpdate: UpdateDefendantDto, + type: CaseType, courtCaseNumber?: string, ) => Promise @@ -52,6 +53,7 @@ describe('DefendantController - Update', () => { givenWhenThen = async ( defendantUpdate: UpdateDefendantDto, + type: CaseType, courtCaseNumber?: string, ) => { const then = {} as Then @@ -61,7 +63,7 @@ describe('DefendantController - Update', () => { caseId, defendantId, user, - { id: caseId, courtCaseNumber, type: CaseType.INDICTMENT } as Case, + { id: caseId, courtCaseNumber, type } as Case, defendant, defendantUpdate, ) @@ -81,7 +83,7 @@ describe('DefendantController - Update', () => { const mockUpdate = mockDefendantModel.update as jest.Mock mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - then = await givenWhenThen(defendantUpdate) + then = await givenWhenThen(defendantUpdate, CaseType.CUSTODY) }) it('should update the defendant without queuing', () => { @@ -102,7 +104,7 @@ describe('DefendantController - Update', () => { const mockUpdate = mockDefendantModel.update as jest.Mock mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - await givenWhenThen(defendantUpdate, uuid()) + await givenWhenThen(defendantUpdate, CaseType.INDICTMENT, uuid()) }) it('should not queue', () => { @@ -118,7 +120,7 @@ describe('DefendantController - Update', () => { const mockUpdate = mockDefendantModel.update as jest.Mock mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - await givenWhenThen(defendantUpdate, uuid()) + await givenWhenThen(defendantUpdate, CaseType.CUSTODY, uuid()) }) it('should queue messages', () => { @@ -141,7 +143,7 @@ describe('DefendantController - Update', () => { const mockUpdate = mockDefendantModel.update as jest.Mock mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - await givenWhenThen(defendantUpdate, uuid()) + await givenWhenThen(defendantUpdate, CaseType.TELECOMMUNICATIONS, uuid()) }) it('should queue messages', () => { @@ -162,29 +164,6 @@ describe('DefendantController - Update', () => { }) }) - describe(`defendant's defender email changed after case is delivered to court`, () => { - const defendantUpdate = { defenderEmail: uuid() } - const updatedDefendant = { ...defendant, ...defendantUpdate } - - beforeEach(async () => { - const mockUpdate = mockDefendantModel.update as jest.Mock - mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - - await givenWhenThen(defendantUpdate, uuid()) - }) - - it('should queue messages', () => { - expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ - { - type: MessageType.DELIVERY_TO_COURT_DEFENDANT, - user, - caseId, - elementId: defendantId, - }, - ]) - }) - }) - describe.each([ { isDefenderChoiceConfirmed: true, shouldSendEmail: true }, { isDefenderChoiceConfirmed: false, shouldSendEmail: false }, @@ -202,7 +181,7 @@ describe('DefendantController - Update', () => { const mockUpdate = mockDefendantModel.update as jest.Mock mockUpdate.mockResolvedValueOnce([1, [updatedDefendant]]) - await givenWhenThen(defendantUpdate, uuid()) + await givenWhenThen(defendantUpdate, CaseType.INDICTMENT, uuid()) }) if (shouldSendEmail) { @@ -228,7 +207,7 @@ describe('DefendantController - Update', () => { let then: Then beforeEach(async () => { - then = await givenWhenThen({}) + then = await givenWhenThen({}, CaseType.CUSTODY) }) it('should throw Error', () => { diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/deliverDefendantToCourtGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/deliverDefendantToCourtGuards.spec.ts new file mode 100644 index 000000000000..69cc2065dd45 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/deliverDefendantToCourtGuards.spec.ts @@ -0,0 +1,19 @@ +import { DefendantExistsGuard } from '../../guards/defendantExists.guard' +import { InternalDefendantController } from '../../internalDefendant.controller' + +describe('InternalDefendantController - Deliver defendant to court guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalDefendantController.prototype.deliverDefendantToCourt, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(1) + expect(new guards[0]()).toBeInstanceOf(DefendantExistsGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendant.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendant.spec.ts new file mode 100644 index 000000000000..1d6c8e73cf85 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendant.spec.ts @@ -0,0 +1,77 @@ +import { uuid } from 'uuidv4' + +import { createTestingDefendantModule } from '../createTestingDefendantModule' + +import { Case } from '../../../case' +import { InternalUpdateDefendantDto } from '../../dto/internalUpdateDefendant.dto' +import { Defendant } from '../../models/defendant.model' + +interface Then { + result: Defendant + error: Error +} + +type GivenWhenThen = () => Promise + +describe('InternalDefendantController - Update defendant', () => { + const caseId = uuid() + const defendantId = uuid() + const defendantNationalId = uuid() + const update = { somefield: 'somevalue' } as InternalUpdateDefendantDto + const updatedDefendant = { + id: defendantId, + nationalId: defendantNationalId, + ...update, + } + let mockDefendantModel: typeof Defendant + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const defendant = { + id: defendantId, + nationalId: defendantNationalId, + } as Defendant + const { defendantModel, internalDefendantController } = + await createTestingDefendantModule() + + mockDefendantModel = defendantModel + + const mockUpdate = mockDefendantModel.update as jest.Mock + mockUpdate.mockRejectedValue(new Error('Some error')) + + givenWhenThen = async () => { + const then = {} as Then + + await internalDefendantController + .updateDefendant( + caseId, + defendantNationalId, + { id: caseId, defendants: [defendant] } as Case, + defendant, + update, + ) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('update defendant', () => { + let then: Then + + beforeEach(async () => { + const mockUpdate = mockDefendantModel.update as jest.Mock + mockUpdate.mockResolvedValue([1, [updatedDefendant]]) + + then = await givenWhenThen() + }) + it('should update the defendant', async () => { + expect(mockDefendantModel.update).toHaveBeenCalledWith( + { ...update, isDefenderChoiceConfirmed: false }, + { where: { id: defendantId, caseId }, returning: true }, + ) + expect(then.result).toEqual(updatedDefendant) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendantGuards.spec.ts b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendantGuards.spec.ts new file mode 100644 index 000000000000..a11f2f6b280b --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/defendant/test/internalDefendantController/updateDefendantGuards.spec.ts @@ -0,0 +1,26 @@ +import { indictmentCases } from '@island.is/judicial-system/types' + +import { CaseTypeGuard } from '../../../case' +import { DefendantNationalIdExistsGuard } from '../../guards/defendantNationalIdExists.guard' +import { InternalDefendantController } from '../../internalDefendant.controller' + +describe('InternalDefendantController - Update defendant guards', () => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let guards: any[] + + beforeEach(() => { + guards = Reflect.getMetadata( + '__guards__', + InternalDefendantController.prototype.updateDefendant, + ) + }) + + it('should have the right guard configuration', () => { + expect(guards).toHaveLength(2) + expect(guards[0]).toBeInstanceOf(CaseTypeGuard) + expect(guards[0]).toEqual({ + allowedCaseTypes: indictmentCases, + }) + expect(new guards[1]()).toBeInstanceOf(DefendantNationalIdExistsGuard) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.service.ts index 5249a63c693d..14710892f483 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.service.ts @@ -1,5 +1,3 @@ -import { MessageDescriptor } from '@formatjs/intl' - import { Inject, Injectable, @@ -12,7 +10,10 @@ import { EmailService } from '@island.is/email-service' import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { type ConfigType } from '@island.is/nest/config' -import { DEFENDER_INDICTMENT_ROUTE } from '@island.is/judicial-system/consts' +import { + DEFENDER_INDICTMENT_ROUTE, + ROUTE_HANDLER_ROUTE, +} from '@island.is/judicial-system/consts' import { DefendantNotificationType, isIndictmentCase, @@ -50,39 +51,27 @@ export class DefendantNotificationService extends BaseNotificationService { } private async sendEmails( - defendant: Defendant, theCase: Case, notificationType: DefendantNotificationType, - subject: MessageDescriptor, - body: MessageDescriptor, + subject: string, + body: string, + to: { name?: string; email?: string }[], ) { - const courtName = theCase.court?.name - const defenderHasAccessToRVG = !!defendant.defenderNationalId - - const formattedSubject = this.formatMessage(subject, { - courtName, - }) - - const formattedBody = this.formatMessage(body, { - courtName, - courtCaseNumber: theCase.courtCaseNumber, - defenderHasAccessToRVG, - linkStart: ``, - linkEnd: '', - }) const promises: Promise[] = [] - if (defendant.defenderEmail) { - promises.push( - this.sendEmail( - formattedSubject, - formattedBody, - defendant.defenderName, - defendant.defenderEmail, - undefined, - true, - ), - ) + for (const recipient of to) { + if (recipient.email && recipient.name) { + promises.push( + this.sendEmail( + subject, + body, + recipient.name, + recipient.email, + undefined, + true, + ), + ) + } } const recipients = await Promise.all(promises) @@ -90,6 +79,43 @@ export class DefendantNotificationService extends BaseNotificationService { return this.recordNotification(theCase.id, notificationType, recipients) } + private sendDefendantSelectedDefenderNotification( + theCase: Case, + ): Promise { + const formattedSubject = this.formatMessage( + strings.defendantSelectedDefenderSubject, + { + courtCaseNumber: theCase.courtCaseNumber, + }, + ) + + const formattedBody = this.formatMessage( + strings.defendantSelectedDefenderBody, + { + courtCaseNumber: theCase.courtCaseNumber, + linkStart: ``, + linkEnd: '', + }, + ) + + return this.sendEmails( + theCase, + DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER, + formattedSubject, + formattedBody, + [ + { + name: theCase.judge?.name, + email: theCase.judge?.email, + }, + { + name: theCase.registrar?.name, + email: theCase.registrar?.email, + }, + ], + ) + } + private shouldSendDefenderAssignedNotification( theCase: Case, defendant: Defendant, @@ -113,8 +139,8 @@ export class DefendantNotificationService extends BaseNotificationService { } private async sendDefenderAssignedNotification( - defendant: Defendant, theCase: Case, + defendant: Defendant, ): Promise { const shouldSend = this.shouldSendDefenderAssignedNotification( theCase, @@ -122,12 +148,30 @@ export class DefendantNotificationService extends BaseNotificationService { ) if (shouldSend) { + const courtName = theCase.court?.name + const defenderHasAccessToRVG = !!defendant.defenderNationalId + + const formattedSubject = this.formatMessage( + strings.defenderAssignedSubject, + { + courtName, + }, + ) + + const formattedBody = this.formatMessage(strings.defenderAssignedBody, { + courtName, + courtCaseNumber: theCase.courtCaseNumber, + defenderHasAccessToRVG, + linkStart: ``, + linkEnd: '', + }) + return this.sendEmails( - defendant, theCase, DefendantNotificationType.DEFENDER_ASSIGNED, - strings.defenderAssignedSubject, - strings.defenderAssignedBody, + formattedSubject, + formattedBody, + [{ name: defendant.defenderName, email: defendant.defenderEmail }], ) } @@ -137,12 +181,14 @@ export class DefendantNotificationService extends BaseNotificationService { private sendNotification( notificationType: DefendantNotificationType, - defendant: Defendant, theCase: Case, + defendant: Defendant, ): Promise { switch (notificationType) { + case DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER: + return this.sendDefendantSelectedDefenderNotification(theCase) case DefendantNotificationType.DEFENDER_ASSIGNED: - return this.sendDefenderAssignedNotification(defendant, theCase) + return this.sendDefenderAssignedNotification(theCase, defendant) default: throw new InternalServerErrorException( `Invalid notification type: ${notificationType}`, @@ -152,13 +198,13 @@ export class DefendantNotificationService extends BaseNotificationService { async sendDefendantNotification( type: DefendantNotificationType, - defendant: Defendant, theCase: Case, + defendant: Defendant, ): Promise { await this.refreshFormatMessage() try { - return await this.sendNotification(type, defendant, theCase) + return await this.sendNotification(type, theCase, defendant) } catch (error) { this.logger.error('Failed to send notification', error) diff --git a/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.strings.ts b/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.strings.ts index eeeebdae43b3..0bd021c0dc53 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.strings.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/defendantNotification.strings.ts @@ -1,6 +1,19 @@ import { defineMessage } from '@formatjs/intl' export const strings = { + defendantSelectedDefenderSubject: defineMessage({ + id: 'judicial.system.backend:defendant_notifications.defendant_selected_defender_subject', + defaultMessage: 'Val á verjanda í máli {courtCaseNumber}', + description: + 'Subject of the notification sent when the defendant defender choise in an indictment has changed', + }), + defendantSelectedDefenderBody: defineMessage({ + id: 'judicial.system.backend:defendant_notifications.defendant_selected_defender_body', + defaultMessage: + 'Verjandi hefur verið valinn í máli {courtCaseNumber}.

Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}.', + description: + 'Body of the notification sent when the defendant defender choise in an indictment has changed', + }), defenderAssignedSubject: defineMessage({ id: 'judicial.system.backend:defendant_notifications.indictment_defender_assigned_subject', defaultMessage: '{courtName} - aðgangur að máli', diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts index f5def84647e3..53237a099dd2 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.controller.ts @@ -118,8 +118,8 @@ export class InternalNotificationController { return this.defendantNotificationService.sendDefendantNotification( notificationDto.type, - defendant, theCase, + defendant, ) } diff --git a/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.service.ts index 13fad8b63ff5..41a4630f0c92 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.service.ts @@ -56,16 +56,16 @@ export class SubpoenaNotificationService extends BaseNotificationService { courtCaseNumber: theCase.courtCaseNumber, }) + const formattedBody = this.formatMessage(body, { + courtCaseNumber: theCase.courtCaseNumber, + linkStart: ``, + linkEnd: '', + }) + const promises: Promise[] = [] for (const recipient of to) { if (recipient.email && recipient.name) { - const formattedBody = this.formatMessage(body, { - courtCaseNumber: theCase.courtCaseNumber, - linkStart: ``, - linkEnd: '', - }) - promises.push( this.sendEmail( formattedSubject, @@ -134,27 +134,6 @@ export class SubpoenaNotificationService extends BaseNotificationService { ) } - private sendDefendantSelectedDefenderNotification( - theCase: Case, - ): Promise { - return this.sendEmails( - theCase, - SubpoenaNotificationType.DEFENDANT_SELECTED_DEFENDER, - strings.defendantSelectedDefenderSubject, - strings.defendantSelectedDefenderBody, - [ - { - name: theCase.judge?.name, - email: theCase.judge?.email, - }, - { - name: theCase.registrar?.name, - email: theCase.registrar?.email, - }, - ], - ) - } - private sendNotification( type: SubpoenaNotificationType, theCase: Case, @@ -164,8 +143,6 @@ export class SubpoenaNotificationService extends BaseNotificationService { return this.sendServiceSuccessfulNotification(theCase) case SubpoenaNotificationType.SERVICE_FAILED: return this.sendServiceFailedNotification(theCase) - case SubpoenaNotificationType.DEFENDANT_SELECTED_DEFENDER: - return this.sendDefendantSelectedDefenderNotification(theCase) default: throw new InternalServerErrorException( `Invalid notification type: ${type}`, diff --git a/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.strings.ts b/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.strings.ts index 8f08b6f475ae..0b475708668f 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.strings.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/subpoenaNotification.strings.ts @@ -27,17 +27,4 @@ export const strings = { description: 'Body of the notification sent when the serive status in an indictment has changed', }), - defendantSelectedDefenderSubject: defineMessage({ - id: 'judicial.system.backend:subpoena_notifications.defendant_selected_defender_subject', - defaultMessage: 'Val á verjanda í máli {courtCaseNumber}', - description: - 'Subject of the notification sent when the serive status in an indictment has changed', - }), - defendantSelectedDefenderBody: defineMessage({ - id: 'judicial.system.backend:subpoena_notifications.defendant_selected_defender_body', - defaultMessage: - 'Verjandi hefur verið valinn í máli {courtCaseNumber}.

Sjá nánar á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}.', - description: - 'Body of the notification sent when the serive status in an indictment has changed', - }), } diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts b/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts index 80603c97865d..36a14e1be0ef 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/dto/updateSubpoena.dto.ts @@ -64,9 +64,4 @@ export class UpdateSubpoenaDto { @IsString() @ApiPropertyOptional({ type: String }) readonly requestedDefenderName?: string - - @IsOptional() - @IsBoolean() - @ApiPropertyOptional({ type: Boolean }) - readonly isDefenderChoiceConfirmed?: boolean } 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 411c8f3e4ef3..968440871a92 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 @@ -33,7 +33,7 @@ import { DeliverResponse } from './models/deliver.response' import { Subpoena } from './models/subpoena.model' import { SubpoenaService } from './subpoena.service' -@Controller('api/internal/') +@Controller('api/internal') @ApiTags('internal subpoenas') @UseGuards(TokenGuard) export class InternalSubpoenaController { diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts index da84d9c66c59..da139a356906 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.controller.ts @@ -5,7 +5,6 @@ import { Get, Header, Inject, - InternalServerErrorException, Param, Query, Res, diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts index 57b9d55af2be..31fba1e49734 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/subpoena.module.ts @@ -4,6 +4,7 @@ import { SequelizeModule } from '@nestjs/sequelize' import { MessageModule } from '@island.is/judicial-system/message' import { CaseModule } from '../case/case.module' +import { DefendantModule } from '../defendant/defendant.module' import { Defendant } from '../defendant/models/defendant.model' import { EventModule } from '../event/event.module' import { FileModule } from '../file/file.module' @@ -21,7 +22,7 @@ import { SubpoenaService } from './subpoena.service' forwardRef(() => FileModule), forwardRef(() => MessageModule), forwardRef(() => EventModule), - + forwardRef(() => DefendantModule), SequelizeModule.forFeature([Subpoena, Defendant]), ], controllers: [ 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 8ecf5820db5e..5259ec00dcb0 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 @@ -20,7 +20,6 @@ import { } from '@island.is/judicial-system/message' import { CaseFileCategory, - DefenderChoice, isFailedServiceStatus, isSuccessfulServiceStatus, isTrafficViolationCase, @@ -31,6 +30,7 @@ import { import { Case } from '../case/models/case.model' import { PdfService } from '../case/pdf.service' +import { DefendantService } from '../defendant/defendant.service' import { Defendant } from '../defendant/models/defendant.model' import { EventService } from '../event' import { FileService } from '../file' @@ -71,6 +71,7 @@ export class SubpoenaService { @Inject(forwardRef(() => FileService)) private readonly fileService: FileService, private readonly eventService: EventService, + private readonly defendantService: DefendantService, @Inject(LOGGER_PROVIDER) private readonly logger: Logger, ) {} @@ -111,53 +112,36 @@ export class SubpoenaService { private async addMessagesForSubpoenaUpdateToQueue( subpoena: Subpoena, serviceStatus?: ServiceStatus, - defenderChoice?: DefenderChoice, - defenderNationalId?: string, ): Promise { - const messages: Message[] = [] + let message: Message | undefined = undefined if (serviceStatus && serviceStatus !== subpoena.serviceStatus) { if (isSuccessfulServiceStatus(serviceStatus)) { - messages.push({ + message = { type: MessageType.SUBPOENA_NOTIFICATION, caseId: subpoena.caseId, elementId: [subpoena.defendantId, subpoena.id], body: { type: SubpoenaNotificationType.SERVICE_SUCCESSFUL, }, - }) + } } else if (isFailedServiceStatus(serviceStatus)) { - messages.push({ + message = { type: MessageType.SUBPOENA_NOTIFICATION, caseId: subpoena.caseId, elementId: [subpoena.defendantId, subpoena.id], body: { type: SubpoenaNotificationType.SERVICE_FAILED, }, - }) + } } } - if ( - defenderChoice === DefenderChoice.CHOOSE && - (defenderChoice !== subpoena.defendant?.defenderChoice || - defenderNationalId !== subpoena.defendant?.defenderNationalId) - ) { - messages.push({ - type: MessageType.SUBPOENA_NOTIFICATION, - caseId: subpoena.caseId, - elementId: [subpoena.defendantId, subpoena.id], - body: { - type: SubpoenaNotificationType.DEFENDANT_SELECTED_DEFENDER, - }, - }) - } - - if (messages.length === 0) { + if (!message) { return } - return this.messageService.sendMessagesToQueue(messages) + return this.messageService.sendMessagesToQueue([message]) } async update( @@ -191,55 +175,47 @@ export class SubpoenaService { } if ( - defenderChoice || - defenderNationalId || - requestedDefenderChoice || - requestedDefenderNationalId + subpoena.case && + subpoena.defendant && + (defenderChoice || + defenderNationalId || + defenderName || + defenderEmail || + defenderPhoneNumber || + requestedDefenderChoice || + requestedDefenderNationalId || + requestedDefenderName) ) { // If there is a change in the defender choice after the judge has confirmed the choice, // we need to set the isDefenderChoiceConfirmed to false - const isChangingDefenderChoice = - (defenderChoice && + const resetDefenderChoiceConfirmed = + subpoena.defendant?.isDefenderChoiceConfirmed && + ((defenderChoice && subpoena.defendant?.defenderChoice !== defenderChoice) || - (defenderNationalId && - subpoena.defendant?.defenderNationalId !== defenderNationalId && - subpoena.defendant?.isDefenderChoiceConfirmed) - - const defendantUpdate: Partial = { - defenderChoice, - defenderNationalId, - defenderName, - defenderEmail, - defenderPhoneNumber, - requestedDefenderChoice, - requestedDefenderNationalId, - requestedDefenderName, - isDefenderChoiceConfirmed: isChangingDefenderChoice ? false : undefined, - } + (defenderNationalId && + subpoena.defendant?.defenderNationalId !== defenderNationalId)) - const [numberOfAffectedRows] = await this.defendantModel.update( - defendantUpdate, + // Færa message handling í defendant service + await this.defendantService.updateRestricted( + subpoena.case, + subpoena.defendant, { - where: { id: subpoena.defendantId }, - transaction, + defenderChoice, + defenderNationalId, + defenderName, + defenderEmail, + defenderPhoneNumber, + requestedDefenderChoice, + requestedDefenderNationalId, + requestedDefenderName, }, + resetDefenderChoiceConfirmed ? false : undefined, + transaction, ) - - if (numberOfAffectedRows > 1) { - // Tolerate failure, but log error - this.logger.error( - `Unexpected number of rows ${numberOfAffectedRows} affected when updating defendant`, - ) - } } // No need to wait for this to finish - this.addMessagesForSubpoenaUpdateToQueue( - subpoena, - serviceStatus, - defenderChoice, - defenderNationalId, - ) + this.addMessagesForSubpoenaUpdateToQueue(subpoena, serviceStatus) if ( update.serviceStatus && diff --git a/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts index 4a7f93290f45..e397c3e498ac 100644 --- a/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts +++ b/apps/judicial-system/backend/src/app/modules/subpoena/test/createTestingSubpoenaModule.ts @@ -13,7 +13,7 @@ import { import { MessageService } from '@island.is/judicial-system/message' import { CaseService, PdfService } from '../../case' -import { Defendant } from '../../defendant' +import { Defendant, DefendantService } from '../../defendant' import { EventService } from '../../event' import { FileService } from '../../file' import { PoliceService } from '../../police' @@ -30,6 +30,7 @@ jest.mock('../../case/pdf.service') jest.mock('../../police/police.service') jest.mock('../../file/file.service') jest.mock('../../event/event.service') +jest.mock('../../defendant/defendant.service') jest.mock('@island.is/judicial-system/message') export const createTestingSubpoenaModule = async () => { @@ -49,6 +50,7 @@ export const createTestingSubpoenaModule = async () => { PoliceService, FileService, EventService, + DefendantService, { provide: LOGGER_PROVIDER, useValue: { diff --git a/libs/judicial-system/types/src/lib/notification.ts b/libs/judicial-system/types/src/lib/notification.ts index 89d0dcdc5f71..981ab499ff70 100644 --- a/libs/judicial-system/types/src/lib/notification.ts +++ b/libs/judicial-system/types/src/lib/notification.ts @@ -21,13 +21,13 @@ export enum CaseNotificationType { } export enum DefendantNotificationType { + DEFENDANT_SELECTED_DEFENDER = 'DEFENDANT_SELECTED_DEFENDER', DEFENDER_ASSIGNED = 'DEFENDER_ASSIGNED', } export enum SubpoenaNotificationType { SERVICE_SUCCESSFUL = 'SERVICE_SUCCESSFUL', SERVICE_FAILED = 'SERVICE_FAILED', - DEFENDANT_SELECTED_DEFENDER = 'DEFENDANT_SELECTED_DEFENDER', } export enum InstitutionNotificationType { @@ -54,10 +54,10 @@ export enum NotificationType { INDICTMENT_DENIED = CaseNotificationType.INDICTMENT_DENIED, INDICTMENT_RETURNED = CaseNotificationType.INDICTMENT_RETURNED, CASE_FILES_UPDATED = CaseNotificationType.CASE_FILES_UPDATED, + DEFENDANT_SELECTED_DEFENDER = DefendantNotificationType.DEFENDANT_SELECTED_DEFENDER, DEFENDER_ASSIGNED = DefendantNotificationType.DEFENDER_ASSIGNED, SERVICE_SUCCESSFUL = SubpoenaNotificationType.SERVICE_SUCCESSFUL, SERVICE_FAILED = SubpoenaNotificationType.SERVICE_FAILED, - DEFENDANT_SELECTED_DEFENDER = SubpoenaNotificationType.DEFENDANT_SELECTED_DEFENDER, INDICTMENTS_WAITING_FOR_CONFIRMATION = InstitutionNotificationType.INDICTMENTS_WAITING_FOR_CONFIRMATION, } From a6930229077731e973cb2f2a649040d6c9925483 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 15 Nov 2024 14:35:01 +0000 Subject: [PATCH 5/6] feat(native-app): add third party error template and use in health overview (#16860) * feat: add third party error template and use in health overview * fix: use arrow function instead of function --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/native/app/src/messages/en.ts | 3 + apps/native/app/src/messages/is.ts | 2 + .../src/screens/health/health-overview.tsx | 360 ++++++++++-------- .../app/src/stores/organizations-store.ts | 5 + .../src/ui/lib/problem/problem-template.tsx | 22 +- .../native/app/src/ui/lib/problem/problem.tsx | 21 +- .../lib/problem/third-party-service-error.tsx | 30 ++ .../utils/get-organization-slug-from-error.ts | 36 ++ 8 files changed, 319 insertions(+), 160 deletions(-) create mode 100644 apps/native/app/src/ui/lib/problem/third-party-service-error.tsx create mode 100644 apps/native/app/src/utils/get-organization-slug-from-error.ts diff --git a/apps/native/app/src/messages/en.ts b/apps/native/app/src/messages/en.ts index 734168b2c9bb..17260fc7d745 100644 --- a/apps/native/app/src/messages/en.ts +++ b/apps/native/app/src/messages/en.ts @@ -594,6 +594,9 @@ export const en: TranslatedMessages = { 'problem.offline.title': 'No internet connection', 'problem.offline.message': 'An error occurred while communicating with the service provider', + 'problem.thirdParty.title': 'No connection', + 'problem.thirdParty.message': + 'An error occurred while communicating with the service provider', // passkeys 'passkeys.headingTitle': 'Sign in with Island.is app', diff --git a/apps/native/app/src/messages/is.ts b/apps/native/app/src/messages/is.ts index 0135ba4a659e..bd6967d93b48 100644 --- a/apps/native/app/src/messages/is.ts +++ b/apps/native/app/src/messages/is.ts @@ -595,6 +595,8 @@ export const is = { 'Ef þú telur þig eiga gögn sem ættu að birtast hér, vinsamlegast hafðu samband við þjónustuaðila.', 'problem.offline.title': 'Samband næst ekki', 'problem.offline.message': 'Villa kom upp í samskiptum við þjónustuaðila', + 'problem.thirdParty.title': 'Samband næst ekki', + 'problem.thirdParty.message': 'Villa kom upp í samskiptum við þjónustuaðila', // passkeys 'passkeys.headingTitle': 'Innskrá með Ísland.is appinu', diff --git a/apps/native/app/src/screens/health/health-overview.tsx b/apps/native/app/src/screens/health/health-overview.tsx index 25fce0d06d70..2c6778c7fb34 100644 --- a/apps/native/app/src/screens/health/health-overview.tsx +++ b/apps/native/app/src/screens/health/health-overview.tsx @@ -1,4 +1,12 @@ -import { Alert, Button, Heading, Input, InputRow, Typography } from '@ui' +import { + Alert, + Button, + Heading, + Input, + InputRow, + Problem, + Typography, +} from '@ui' import React, { useCallback, useMemo, useState } from 'react' import { FormattedMessage, useIntl } from 'react-intl' import { @@ -12,6 +20,7 @@ import { } from 'react-native' import { NavigationFunctionComponent } from 'react-native-navigation' import styled, { useTheme } from 'styled-components/native' +import { ApolloError } from '@apollo/client' import { useGetHealthCenterQuery, @@ -77,6 +86,10 @@ const HeadingSection: React.FC = ({ title, onPress }) => { ) } +const showErrorComponent = (error: ApolloError) => { + return +} + const { getNavigationOptions, useNavigationOptions } = createNavigationOptionHooks((theme, intl) => ({ topBar: { @@ -247,49 +260,57 @@ export const HealthOverviewScreen: NavigationFunctionComponent = ({ ) } /> - - - - - - + {(healthCenterRes.data || healthCenterRes.loading) && ( + <> + + + + + + + + )} + {healthCenterRes.error && + !healthCenterRes.data && + showErrorComponent(healthCenterRes.error)} openBrowser(`${origin}/minarsidur/heilsa/yfirlit`, componentId) } /> - {healthInsuranceData?.isInsured || healthInsuranceRes.loading ? ( + {(healthInsuranceRes.data && healthInsuranceData?.isInsured) || + healthInsuranceRes.loading ? ( ) : ( - + !healthInsuranceRes.error && + healthInsuranceRes.data && ( + + ) )} + {healthInsuranceRes.error && + !healthInsuranceRes.data && + showErrorComponent(healthInsuranceRes.error)} - - - - - - - - - - + {(paymentOverviewRes.loading || paymentOverviewRes.data) && ( + <> + + + + + + + + + + + + )} + {paymentOverviewRes.error && + !paymentOverviewRes.data && + paymentStatusRes.error && + !paymentStatusRes.data && + showErrorComponent(paymentOverviewRes.error)} - - - - - - + {(medicinePurchaseRes.loading || medicinePurchaseRes.data) && ( + <> + + + + + + + + )} + {medicinePurchaseRes.error && + !medicinePurchaseRes.data && + showErrorComponent(medicinePurchaseRes.error)} diff --git a/apps/native/app/src/stores/organizations-store.ts b/apps/native/app/src/stores/organizations-store.ts index 2d3580092443..3b9cb837e73a 100644 --- a/apps/native/app/src/stores/organizations-store.ts +++ b/apps/native/app/src/stores/organizations-store.ts @@ -30,6 +30,7 @@ interface Organization { interface OrganizationsStore extends State { organizations: Organization[] getOrganizationLogoUrl(forName: string, size?: number): ImageSourcePropType + getOrganizationNameBySlug(slug: string): string actions: any } @@ -72,6 +73,10 @@ export const organizationsStore = create( const uri = `${url}?w=${size}&h=${size}&fit=pad&fm=png` return { uri } }, + getOrganizationNameBySlug(slug: string) { + const org = get().organizations.find((o) => o.slug === slug) + return org?.title ?? '' + }, actions: { updateOriganizations: async () => { const client = await getApolloClientAsync() diff --git a/apps/native/app/src/ui/lib/problem/problem-template.tsx b/apps/native/app/src/ui/lib/problem/problem-template.tsx index b15358fbdc05..1ebe261c581f 100644 --- a/apps/native/app/src/ui/lib/problem/problem-template.tsx +++ b/apps/native/app/src/ui/lib/problem/problem-template.tsx @@ -10,6 +10,7 @@ export type ProblemTemplateBaseProps = { title: string message: string | ReactNode withContainer?: boolean + size?: 'small' | 'large' } interface WithIconProps extends ProblemTemplateBaseProps { @@ -68,6 +69,7 @@ const getColorsByVariant = ( const Host = styled.View<{ borderColor: Colors noContainer?: boolean + size: 'small' | 'large' }>` border-color: ${({ borderColor, theme }) => theme.color[borderColor]}; border-width: 1px; @@ -76,11 +78,12 @@ const Host = styled.View<{ justify-content: center; align-items: center; flex: 1; - row-gap: ${({ theme }) => theme.spacing[3]}px; + row-gap: ${({ theme, size }) => + size === 'small' ? theme.spacing[2] : theme.spacing[3]}px; padding: ${({ theme }) => theme.spacing[2]}px; ${({ noContainer, theme }) => noContainer && `margin: ${theme.spacing[2]}px;`} - min-height: 280px; + min-height: ${({ size }) => (size === 'large' ? '280' : '142')}px; ` const Tag = styled(View)<{ @@ -114,12 +117,13 @@ export const ProblemTemplate = ({ showIcon, tag, withContainer, + size = 'large', }: ProblemTemplateProps) => { const { borderColor, tagColor, tagBackgroundColor } = getColorsByVariant(variant) return ( - + {tag && ( @@ -129,10 +133,18 @@ export const ProblemTemplate = ({ )} {showIcon && } - + {title} - {message} + + {message} + ) diff --git a/apps/native/app/src/ui/lib/problem/problem.tsx b/apps/native/app/src/ui/lib/problem/problem.tsx index 40963f69836c..4fa3d4e984d1 100644 --- a/apps/native/app/src/ui/lib/problem/problem.tsx +++ b/apps/native/app/src/ui/lib/problem/problem.tsx @@ -1,7 +1,10 @@ import { useEffect } from 'react' + import { useTranslate } from '../../../hooks/use-translate' import { useOfflineStore } from '../../../stores/offline-store' import { ProblemTemplate, ProblemTemplateBaseProps } from './problem-template' +import { getOrganizationSlugFromError } from '../../../utils/get-organization-slug-from-error' +import { ThirdPartyServiceError } from './third-party-service-error' enum ProblemTypes { error = 'error', @@ -20,7 +23,7 @@ type ProblemBaseProps = { title?: string message?: string logError?: boolean -} & Pick +} & Pick interface ErrorProps extends ProblemBaseProps { type?: 'error' @@ -61,6 +64,7 @@ export const Problem = ({ logError = false, withContainer, showIcon, + size = 'large', }: ProblemProps) => { const t = useTranslate() const { isConnected } = useOfflineStore() @@ -73,6 +77,7 @@ export const Problem = ({ message: message ?? t('problem.error.message'), tag: tag ?? t('problem.error.tag'), variant: 'error', + size: size ?? 'large', } as const useEffect(() => { @@ -90,6 +95,7 @@ export const Problem = ({ variant="warning" title={title ?? t('problem.offline.title')} message={message ?? t('problem.offline.message')} + size={size} /> ) } @@ -99,6 +105,18 @@ export const Problem = ({ switch (type) { case ProblemTypes.error: + if (error) { + const organizationSlug = getOrganizationSlugFromError(error) + + if (organizationSlug) { + return ( + + ) + } + } return case ProblemTypes.noData: @@ -109,6 +127,7 @@ export const Problem = ({ variant="info" title={title ?? t('problem.noData.title')} message={message ?? t('problem.noData.message')} + size={size} /> ) diff --git a/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx b/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx new file mode 100644 index 000000000000..d2a714c18694 --- /dev/null +++ b/apps/native/app/src/ui/lib/problem/third-party-service-error.tsx @@ -0,0 +1,30 @@ +import React from 'react' +import { useIntl } from 'react-intl' + +import { ProblemTemplate } from './problem-template' +import { useOrganizationsStore } from '../../../stores/organizations-store' + +type ThirdPartyServiceErrorProps = { + organizationSlug: string + size: 'small' | 'large' +} + +export const ThirdPartyServiceError = ({ + organizationSlug, + size, +}: ThirdPartyServiceErrorProps) => { + const intl = useIntl() + + const { getOrganizationNameBySlug } = useOrganizationsStore() + const organizationName = getOrganizationNameBySlug(organizationSlug) + + return ( + + ) +} diff --git a/apps/native/app/src/utils/get-organization-slug-from-error.ts b/apps/native/app/src/utils/get-organization-slug-from-error.ts new file mode 100644 index 000000000000..1463c87cb8db --- /dev/null +++ b/apps/native/app/src/utils/get-organization-slug-from-error.ts @@ -0,0 +1,36 @@ +import { ApolloError } from '@apollo/client' + +type PartialProblem = { + organizationSlug?: string +} + +type CustomExtension = { + code: string + problem?: PartialProblem + exception?: { + problem?: PartialProblem + } +} + +/** + * Extracts the organization slug from the Apollo error, if it exists. + */ +export const getOrganizationSlugFromError = (error: ApolloError | unknown) => { + const graphQLErrors = (error as ApolloError)?.graphQLErrors + + if (graphQLErrors) { + for (const graphQLError of graphQLErrors) { + const extensions = graphQLError.extensions as CustomExtension + + const organizationSlug = + extensions?.problem?.organizationSlug ?? + extensions?.exception?.problem?.organizationSlug + + if (organizationSlug) { + return organizationSlug + } + } + } + + return undefined +} From b9cf31e8ef7a10426b84e98ec484df266982c03a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81sd=C3=ADs=20Erna=20Gu=C3=B0mundsd=C3=B3ttir?= Date: Fri, 15 Nov 2024 15:23:46 +0000 Subject: [PATCH 6/6] feat(my-pages): add info and deadline text (#16896) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../law-and-order/src/lib/law-and-order.service.ts | 2 ++ .../domains/law-and-order/src/models/subpoena.model.ts | 8 ++++---- libs/clients/judicial-system-sp/src/clientConfig.json | 4 ++++ .../law-and-order/src/screens/Subpoena/Subpoena.graphql | 2 ++ .../law-and-order/src/screens/Subpoena/Subpoena.tsx | 8 ++++++-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/libs/api/domains/law-and-order/src/lib/law-and-order.service.ts b/libs/api/domains/law-and-order/src/lib/law-and-order.service.ts index ca8f4038a1e0..d773090945c3 100644 --- a/libs/api/domains/law-and-order/src/lib/law-and-order.service.ts +++ b/libs/api/domains/law-and-order/src/lib/law-and-order.service.ts @@ -162,6 +162,8 @@ export class LawAndOrderService { (alert) => alert.type === AlertMessageTypeEnum.Success, )?.message, description: subpoenaData.subtitle, + information: subpoenaData.subpoenaInfoText, + deadline: subpoenaData.subpoenaNotificationDeadline, }, } return data diff --git a/libs/api/domains/law-and-order/src/models/subpoena.model.ts b/libs/api/domains/law-and-order/src/models/subpoena.model.ts index c71f0f365388..d54a1af82517 100644 --- a/libs/api/domains/law-and-order/src/models/subpoena.model.ts +++ b/libs/api/domains/law-and-order/src/models/subpoena.model.ts @@ -5,9 +5,6 @@ import { Group } from './group.model' @ObjectType('LawAndOrderSubpoenaTexts') export class Text { - @Field({ nullable: true }) - intro?: string - @Field({ nullable: true }) confirmation?: string @@ -15,7 +12,10 @@ export class Text { description?: string @Field({ nullable: true }) - claim?: string + information?: string + + @Field({ nullable: true }) + deadline?: string } @ObjectType('LawAndOrderSubpoenaData') diff --git a/libs/clients/judicial-system-sp/src/clientConfig.json b/libs/clients/judicial-system-sp/src/clientConfig.json index 48c81122e582..2e4d54e84c54 100644 --- a/libs/clients/judicial-system-sp/src/clientConfig.json +++ b/libs/clients/judicial-system-sp/src/clientConfig.json @@ -450,6 +450,8 @@ "type": "object", "properties": { "title": { "type": "string" }, + "subpoenaInfoText": { "type": "string" }, + "subpoenaNotificationDeadline": { "type": "string" }, "subtitle": { "type": "string" }, "groups": { "type": "array", @@ -468,6 +470,8 @@ }, "required": [ "title", + "subpoenaInfoText", + "subpoenaNotificationDeadline", "subtitle", "groups", "alerts", diff --git a/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.graphql b/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.graphql index 1551e0e65a5f..5134408d6183 100644 --- a/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.graphql +++ b/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.graphql @@ -21,6 +21,8 @@ query GetSubpoena($input: LawAndOrderSubpoenaInput!, $locale: String!) { texts { confirmation description + information + deadline } } } diff --git a/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.tsx b/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.tsx index 819da2fc84f4..06b836951131 100644 --- a/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.tsx +++ b/libs/portals/my-pages/law-and-order/src/screens/Subpoena/Subpoena.tsx @@ -134,9 +134,13 @@ const Subpoena = () => { )} - {formatMessage(messages.subpoenaInfoText)} + {subpoena.texts?.information ?? + formatMessage(messages.subpoenaInfoText)} + + + {subpoena.texts?.deadline ?? + formatMessage(messages.subpoenaInfoText2)} - {formatMessage(messages.subpoenaInfoText2)} {!loading && subpoena.data.hasChosen === false && (