diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index 9777fdd1be49..fe12aea5011d 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -859,4 +859,17 @@ export const notifications = { description: 'Texti í pósti til dómstóls þegar ákæra er afturkölluð', }, }), + caseFilesUpdated: defineMessages({ + subject: { + id: 'judicial.system.backend:notifications.case_files_updated.subject', + defaultMessage: 'Ný gögn í máli {courtCaseNumber}', + description: 'Fyrirsögn í pósti til aðila máls þegar ný gögn eru send', + }, + body: { + id: 'judicial.system.backend:notifications.case_files_updated.body', + defaultMessage: + 'Ný gögn hafa borist vegna máls {courtCaseNumber}. {userHasAccessToRVG, select, true {Hægt er að nálgast gögn málsins á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}} other {Hægt er að nálgast gögn málsins hjá {court} ef þau hafa ekki þegar verið afhent}}.', + description: 'Texti í pósti til aðila máls þegar ný gögn eru send', + }, + }), } diff --git a/apps/judicial-system/backend/src/app/modules/notification/guards/rolesRules.ts b/apps/judicial-system/backend/src/app/modules/notification/guards/rolesRules.ts index 284c03caf5c1..dd5a3153797e 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/guards/rolesRules.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/guards/rolesRules.ts @@ -10,6 +10,7 @@ export const prosecutorNotificationRule: RolesRule = { NotificationType.HEADS_UP, NotificationType.READY_FOR_COURT, NotificationType.APPEAL_CASE_FILES_UPDATED, + NotificationType.CASE_FILES_UPDATED, ], } as RolesRule @@ -18,7 +19,10 @@ export const defenderNotificationRule: RolesRule = { role: UserRole.DEFENDER, type: RulesType.FIELD_VALUES, dtoField: 'type', - dtoFieldValues: [NotificationType.APPEAL_CASE_FILES_UPDATED], + dtoFieldValues: [ + NotificationType.APPEAL_CASE_FILES_UPDATED, + NotificationType.CASE_FILES_UPDATED, + ], } as RolesRule // Allows district court judges to send notifiications diff --git a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts index b8b670d07c24..40313e0c3df0 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/internalNotification.service.ts @@ -47,7 +47,6 @@ import { RequestSharedWithDefender, SessionArrangements, type User, - UserRole, } from '@island.is/judicial-system/types' import { @@ -75,7 +74,11 @@ import { import { notifications } from '../../messages' import { type Case, DateLog } from '../case' import { CourtService } from '../court' -import { type Defendant, DefendantService } from '../defendant' +import { + type CivilClaimant, + type Defendant, + DefendantService, +} from '../defendant' import { EventService } from '../event' import { DeliverResponse } from './models/deliver.response' import { Notification, Recipient } from './models/notification.model' @@ -1521,7 +1524,9 @@ export class InternalNotificationService extends BaseNotificationService { SessionArrangements.ALL_PRESENT_SPOKESPERSON, ].includes(theCase.sessionArrangements) - if (!isDefenderIncludedInSessionArrangements) return false + if (!isDefenderIncludedInSessionArrangements) { + return false + } } else { const hasDefenderBeenNotified = this.hasReceivedNotification( [ @@ -1609,10 +1614,11 @@ export class InternalNotificationService extends BaseNotificationService { } = civilClaimant const shouldSend = + hasSpokesperson && this.shouldSendAdvocateAssignedNotification( theCase, spokespersonEmail, - ) && hasSpokesperson + ) if (shouldSend === true) { promises.push( @@ -1757,6 +1763,116 @@ export class InternalNotificationService extends BaseNotificationService { } //#endregion + //#region CASE_FILES_UPDATED notifications + private sendCaseFilesUpdatedNotification( + courtCaseNumber?: string, + court?: string, + link?: string, + name?: string, + email?: string, + ) { + const subject = this.formatMessage(notifications.caseFilesUpdated.subject, { + courtCaseNumber, + }) + const html = this.formatMessage(notifications.caseFilesUpdated.body, { + courtCaseNumber, + court: court?.replace('dómur', 'dómi'), + userHasAccessToRVG: Boolean(link), + linkStart: ``, + linkEnd: '', + }) + + return this.sendEmail(subject, html, name, email, undefined, !link) + } + + private async sendCaseFilesUpdatedNotifications( + theCase: Case, + user: User, + ): Promise { + const promises = [ + this.sendCaseFilesUpdatedNotification( + theCase.courtCaseNumber, + theCase.court?.name, + `${this.config.clientUrl}${INDICTMENTS_COURT_OVERVIEW_ROUTE}/${theCase.id}`, + theCase.judge?.name, + theCase.judge?.email, + ), + ] + + const uniqueSpokespersons = _uniqBy( + theCase.civilClaimants?.filter((c) => c.hasSpokesperson) ?? [], + (c: CivilClaimant) => c.spokespersonEmail, + ) + uniqueSpokespersons.forEach((civilClaimant) => { + if (civilClaimant.spokespersonEmail) { + promises.push( + this.sendCaseFilesUpdatedNotification( + theCase.courtCaseNumber, + theCase.court?.name, + civilClaimant.spokespersonNationalId && + formatDefenderRoute( + this.config.clientUrl, + theCase.type, + theCase.id, + ), + civilClaimant.spokespersonName, + civilClaimant.spokespersonEmail, + ), + ) + } + }) + + if (isProsecutionUser(user)) { + const uniqueDefendants = _uniqBy( + theCase.defendants ?? [], + (d: Defendant) => d.defenderEmail, + ) + uniqueDefendants.forEach((defendant) => { + if (defendant.defenderEmail) { + promises.push( + this.sendCaseFilesUpdatedNotification( + theCase.courtCaseNumber, + theCase.court?.name, + defendant.defenderNationalId && + formatDefenderRoute( + this.config.clientUrl, + theCase.type, + theCase.id, + ), + defendant.defenderName, + defendant.defenderEmail, + ), + ) + } + }) + } + + if (isDefenceUser(user)) { + promises.push( + this.sendCaseFilesUpdatedNotification( + theCase.courtCaseNumber, + theCase.court?.name, + `${this.config.clientUrl}${INDICTMENTS_OVERVIEW_ROUTE}/${theCase.id}`, + theCase.prosecutor?.name, + theCase.prosecutor?.email, + ), + ) + } + + const recipients = await Promise.all(promises) + + if (recipients.length > 0) { + return this.recordNotification( + theCase.id, + NotificationType.CASE_FILES_UPDATED, + recipients, + ) + } + + return { delivered: true } + } + //#endregion + //#region Appeal notifications //#region COURT_OF_APPEAL_JUDGE_ASSIGNED notifications private async sendCourtOfAppealJudgeAssignedNotification( @@ -1851,7 +1967,7 @@ export class InternalNotificationService extends BaseNotificationService { ) } - if (user.role === UserRole.DEFENDER) { + if (isDefenceUser(user)) { promises.push( this.sendEmail( subject, @@ -1863,7 +1979,7 @@ export class InternalNotificationService extends BaseNotificationService { promises.push(this.sendSms(smsText, theCase.prosecutor?.mobileNumber)) } - if (user.role === UserRole.PROSECUTOR && theCase.defenderEmail) { + if (isProsecutionUser(user) && theCase.defenderEmail) { const url = theCase.defenderNationalId && formatDefenderRoute(this.config.clientUrl, theCase.type, theCase.id) @@ -2081,7 +2197,7 @@ export class InternalNotificationService extends BaseNotificationService { } } - if (user.role === UserRole.DEFENDER) { + if (isDefenceUser(user)) { const prosecutorHtml = this.formatMessage( notifications.caseAppealStatement.body, { @@ -2103,7 +2219,7 @@ export class InternalNotificationService extends BaseNotificationService { ) } - if (user.role === UserRole.PROSECUTOR && theCase.defenderEmail) { + if (isProsecutionUser(user)) { const url = theCase.defenderNationalId && formatDefenderRoute(this.config.clientUrl, theCase.type, theCase.id) @@ -2186,7 +2302,7 @@ export class InternalNotificationService extends BaseNotificationService { } }) - if (user.role === UserRole.DEFENDER) { + if (isDefenceUser(user)) { const prosecutorHtml = this.formatMessage( notifications.caseAppealCaseFilesUpdated.body, { @@ -2563,6 +2679,8 @@ export class InternalNotificationService extends BaseNotificationService { return this.sendIndictmentDeniedNotifications(theCase) case NotificationType.INDICTMENT_RETURNED: return this.sendIndictmentReturnedNotifications(theCase) + case NotificationType.CASE_FILES_UPDATED: + return this.sendCaseFilesUpdatedNotifications(theCase, user) default: throw new InternalServerErrorException( `Invalid notification type ${type}`, diff --git a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts index 46665cef3181..be058b8463e2 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/notification.service.ts @@ -75,6 +75,7 @@ export class NotificationService { case NotificationType.ADVOCATE_ASSIGNED: case NotificationType.APPEAL_JUDGES_ASSIGNED: case NotificationType.APPEAL_CASE_FILES_UPDATED: + case NotificationType.CASE_FILES_UPDATED: messages = [this.getNotificationMessage(type, user, theCase)] break default: diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealStatementNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealStatementNotifications.spec.ts index db0ccf911fd5..7d7bdb6aff13 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealStatementNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealStatementNotifications.spec.ts @@ -3,6 +3,7 @@ import { uuid } from 'uuidv4' import { EmailService } from '@island.is/email-service' import { + InstitutionType, NotificationType, User, UserRole, @@ -19,13 +20,12 @@ interface Then { } type GivenWhenThen = ( - role: UserRole, + user: User, defenderNationalId?: string, appealsCourtNumber?: string, ) => Promise describe('InternalNotificationController - Send appeal statement notifications', () => { - const userId = uuid() const caseId = uuid() const prosecutorName = uuid() const prosecutorEmail = uuid() @@ -54,7 +54,7 @@ describe('InternalNotificationController - Send appeal statement notifications', mockEmailService = emailService givenWhenThen = async ( - role: UserRole, + user: User, defenderNationalId?: string, appealCaseNumber?: string, ) => { @@ -77,7 +77,7 @@ describe('InternalNotificationController - Send appeal statement notifications', appealJudge1: { name: judgeName1, email: judgeEmail1 }, } as Case, { - user: { id: userId, role } as User, + user, type: NotificationType.APPEAL_STATEMENT, }, ) @@ -99,7 +99,14 @@ describe('InternalNotificationController - Send appeal statement notifications', let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.PROSECUTOR, uuid(), appealCaseNumber) + then = await givenWhenThen( + { + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User, + uuid(), + appealCaseNumber, + ) }) it('should send notification to appeals court and defender', () => { @@ -123,7 +130,10 @@ describe('InternalNotificationController - Send appeal statement notifications', beforeEach(async () => { then = await givenWhenThen( - UserRole.PROSECUTOR, + { + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User, undefined, appealCaseNumber, ) @@ -149,7 +159,11 @@ describe('InternalNotificationController - Send appeal statement notifications', let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.DEFENDER, uuid(), appealCaseNumber) + then = await givenWhenThen( + { role: UserRole.DEFENDER } as User, + uuid(), + appealCaseNumber, + ) }) it('should send notification to appeals court and prosecutor', () => { @@ -172,7 +186,13 @@ describe('InternalNotificationController - Send appeal statement notifications', let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.PROSECUTOR, uuid()) + then = await givenWhenThen( + { + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User, + uuid(), + ) }) it('should send notification to defender', () => { @@ -191,7 +211,10 @@ describe('InternalNotificationController - Send appeal statement notifications', let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.PROSECUTOR) + then = await givenWhenThen({ + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User) }) it('should send notification to defender', () => { @@ -210,7 +233,7 @@ describe('InternalNotificationController - Send appeal statement notifications', let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.DEFENDER, uuid()) + then = await givenWhenThen({ role: UserRole.DEFENDER } as User, uuid()) }) it('should send notification to prosecutor', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealToCourtOfAppealsNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealToCourtOfAppealsNotifications.spec.ts index 65bdd8473fb1..79b89a75a0f3 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealToCourtOfAppealsNotifications.spec.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendAppealToCourtOfAppealsNotifications.spec.ts @@ -4,6 +4,7 @@ import { EmailService } from '@island.is/email-service' import { SmsService } from '@island.is/nova-sms' import { + InstitutionType, NotificationType, User, UserRole, @@ -19,13 +20,9 @@ interface Then { error: Error } -type GivenWhenThen = ( - role: UserRole, - defenderNationalId?: string, -) => Promise +type GivenWhenThen = (user: User, defenderNationalId?: string) => Promise describe('InternalNotificationController - Send appeal to court of appeals notifications', () => { - const userId = uuid() const caseId = uuid() const prosecutorName = uuid() const prosecutorEmail = uuid() @@ -56,7 +53,7 @@ describe('InternalNotificationController - Send appeal to court of appeals notif mockEmailService = emailService mockSmsService = smsService - givenWhenThen = async (role: UserRole, defenderNationalId?: string) => { + givenWhenThen = async (user: User, defenderNationalId?: string) => { const then = {} as Then await internalNotificationController @@ -79,7 +76,7 @@ describe('InternalNotificationController - Send appeal to court of appeals notif courtId: courtId, } as Case, { - user: { id: userId, role } as User, + user, type: NotificationType.APPEAL_TO_COURT_OF_APPEALS, }, ) @@ -93,7 +90,13 @@ describe('InternalNotificationController - Send appeal to court of appeals notif let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.PROSECUTOR, uuid()) + then = await givenWhenThen( + { + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User, + uuid(), + ) }) it('should send notification to judge, registrar, court and defender', () => { @@ -138,7 +141,10 @@ describe('InternalNotificationController - Send appeal to court of appeals notif let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.PROSECUTOR) + then = await givenWhenThen({ + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as User) }) it('should send notification to judge and defender', () => { @@ -168,7 +174,7 @@ describe('InternalNotificationController - Send appeal to court of appeals notif let then: Then beforeEach(async () => { - then = await givenWhenThen(UserRole.DEFENDER, uuid()) + then = await givenWhenThen({ role: UserRole.DEFENDER } as User, uuid()) }) it('should send notifications to judge and prosecutor', () => { diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCaseFilesUpdatedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCaseFilesUpdatedNotifications.spec.ts new file mode 100644 index 000000000000..10102b2743d5 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/internalNotificationController/sendCaseFilesUpdatedNotifications.spec.ts @@ -0,0 +1,152 @@ +import { uuid } from 'uuidv4' + +import { EmailService } from '@island.is/email-service' + +import { + CaseType, + InstitutionType, + NotificationType, + User, + UserRole, +} from '@island.is/judicial-system/types' + +import { createTestingNotificationModule } from '../createTestingNotificationModule' + +import { Case } from '../../../case' +import { DeliverResponse } from '../../models/deliver.response' + +interface Then { + result: DeliverResponse + error: Error +} + +type GivenWhenThen = (user: User) => Promise + +describe('InternalNotificationController - Send case files updated notifications', () => { + const caseId = uuid() + const courtCaseNumber = uuid() + const prosecutorName = uuid() + const prosecutorEmail = uuid() + const judgeName = uuid() + const judgeEmail = uuid() + const defenderNationalId = uuid() + const defenderName = uuid() + const defenderEmail = uuid() + const spokespersonNationalId = uuid() + const spokespersonName = uuid() + const spokespersonEmail = uuid() + let mockEmailService: EmailService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { emailService, internalNotificationController } = + await createTestingNotificationModule() + + mockEmailService = emailService + + givenWhenThen = async (user: User) => { + const then = {} as Then + + await internalNotificationController + .sendCaseNotification( + caseId, + { + id: caseId, + type: CaseType.INDICTMENT, + courtCaseNumber, + court: { name: 'Héraðsdómur Reykjavíkur' }, + prosecutor: { name: prosecutorName, email: prosecutorEmail }, + judge: { name: judgeName, email: judgeEmail }, + defendants: [{ defenderNationalId, defenderName, defenderEmail }], + civilClaimants: [ + { + hasSpokesperson: true, + spokespersonNationalId, + spokespersonName, + spokespersonEmail, + }, + ], + } as Case, + { + user, + type: NotificationType.CASE_FILES_UPDATED, + }, + ) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('notification sent by prosecutor', () => { + let then: Then + + beforeEach(async () => { + then = await givenWhenThen({ + role: UserRole.PROSECUTOR, + institution: { type: InstitutionType.PROSECUTORS_OFFICE }, + } as unknown as User) + }) + + it('should send notifications', () => { + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: judgeName, address: judgeEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: spokespersonName, address: spokespersonEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: defenderName, address: defenderEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(then.result).toEqual({ delivered: true }) + }) + }) + + describe('notification sent by defender', () => { + let then: Then + + beforeEach(async () => { + then = await givenWhenThen({ + role: UserRole.DEFENDER, + } as User) + }) + + it('should send notifications', () => { + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: judgeName, address: judgeEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: spokespersonName, address: spokespersonEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(mockEmailService.sendEmail).toHaveBeenCalledWith( + expect.objectContaining({ + to: [{ name: prosecutorName, address: prosecutorEmail }], + subject: `Ný gögn í máli ${courtCaseNumber}`, + html: `Ný gögn hafa borist vegna máls ${courtCaseNumber}. Hægt er að nálgast gögn málsins á yfirlitssíðu málsins í Réttarvörslugátt.`, + }), + ) + expect(then.result).toEqual({ delivered: true }) + }) + }) +}) diff --git a/apps/judicial-system/backend/src/app/modules/notification/test/notificationController/sendCaseFilesUpdatedNotifications.spec.ts b/apps/judicial-system/backend/src/app/modules/notification/test/notificationController/sendCaseFilesUpdatedNotifications.spec.ts new file mode 100644 index 000000000000..d2ace5bc1743 --- /dev/null +++ b/apps/judicial-system/backend/src/app/modules/notification/test/notificationController/sendCaseFilesUpdatedNotifications.spec.ts @@ -0,0 +1,69 @@ +import { uuid } from 'uuidv4' + +import { MessageService, MessageType } from '@island.is/judicial-system/message' +import { NotificationType, User } from '@island.is/judicial-system/types' + +import { createTestingNotificationModule } from '../createTestingNotificationModule' + +import { Case } from '../../../case' +import { SendNotificationResponse } from '../../models/sendNotification.response' + +interface Then { + result: SendNotificationResponse + error: Error +} + +type GivenWhenThen = (caseId: string) => Promise + +describe('NotificationController - Send case files updated notification', () => { + const userId = uuid() + const user = { id: userId } as User + + let mockMessageService: MessageService + let givenWhenThen: GivenWhenThen + + beforeEach(async () => { + const { messageService, notificationController } = + await createTestingNotificationModule() + + mockMessageService = messageService + + const mockSendMessagesToQueue = + messageService.sendMessagesToQueue as jest.Mock + mockSendMessagesToQueue.mockResolvedValue(undefined) + + givenWhenThen = async (caseId) => { + const then = {} as Then + + await notificationController + .sendCaseNotification(caseId, user, { id: caseId } as Case, { + type: NotificationType.CASE_FILES_UPDATED, + }) + .then((result) => (then.result = result)) + .catch((error) => (then.error = error)) + + return then + } + }) + + describe('message queued', () => { + const caseId = uuid() + let then: Then + + beforeEach(async () => { + then = await givenWhenThen(caseId) + }) + + it('should send message to queue', () => { + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ + { + type: MessageType.NOTIFICATION, + user, + caseId, + body: { type: NotificationType.CASE_FILES_UPDATED }, + }, + ]) + expect(then.result).toEqual({ notificationSent: true }) + }) + }) +}) diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx index 7cf61ddc35bb..e3643d276fcf 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Completed/Completed.tsx @@ -60,11 +60,11 @@ const Completed: FC = () => { ) const handleNextButtonClick = useCallback(async () => { - const allSucceeded = await handleUpload( + const uploadResult = await handleUpload( uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) - if (!allSucceeded) { + if (uploadResult !== 'ALL_SUCCEEDED') { return } diff --git a/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx index a40748e36aa8..b4f582149df7 100644 --- a/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx +++ b/apps/judicial-system/web/src/routes/Shared/AddFiles/AddFiles.tsx @@ -18,8 +18,12 @@ import { UserContext, } from '@island.is/judicial-system-web/src/components' import UploadFiles from '@island.is/judicial-system-web/src/components/UploadFiles/UploadFiles' -import { CaseFileCategory } from '@island.is/judicial-system-web/src/graphql/schema' import { + CaseFileCategory, + NotificationType, +} from '@island.is/judicial-system-web/src/graphql/schema' +import { + useCase, useS3Upload, useUploadFiles, } from '@island.is/judicial-system-web/src/utils/hooks' @@ -47,6 +51,7 @@ const AddFiles: FC = () => { updateUploadFile, } = useUploadFiles() const { handleUpload } = useS3Upload(workingCase.id) + const { sendNotification } = useCase() const caseFileCategory = isDefenceUser(user) ? CaseFileCategory.DEFENDANT_CASE_FILE @@ -82,15 +87,26 @@ const AddFiles: FC = () => { ) const handleNextButtonClick = useCallback(async () => { - const allSucceeded = await handleUpload( + const uploadResult = await handleUpload( uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) - if (allSucceeded) { + if (uploadResult !== 'NONE_SUCCEEDED') { + // Some files were added successfully so we send a notification + sendNotification(workingCase.id, NotificationType.CASE_FILES_UPDATED) + } + + if (uploadResult === 'ALL_SUCCEEDED') { setVisibleModal('sendFiles') } - }, [handleUpload, updateUploadFile, uploadFiles]) + }, [ + handleUpload, + sendNotification, + updateUploadFile, + uploadFiles, + workingCase.id, + ]) return ( { }/${id}` const handleNextButtonClick = useCallback(async () => { - const allSucceeded = await handleUpload( + const uploadResult = await handleUpload( uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) - if (!allSucceeded) { + if (uploadResult !== 'ALL_SUCCEEDED') { return } diff --git a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx index 72cec8d0db9b..fdea9436e1af 100644 --- a/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx +++ b/apps/judicial-system/web/src/routes/Shared/AppealToCourtOfAppeals/AppealToCourtOfAppeals.tsx @@ -70,12 +70,12 @@ const AppealToCourtOfAppeals = () => { }/${id}` const handleNextButtonClick = useCallback(async () => { - const allSucceeded = await handleUpload( + const uploadResult = await handleUpload( uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) - if (!allSucceeded) { + if (uploadResult !== 'ALL_SUCCEEDED') { return } diff --git a/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx b/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx index 5924ad4e57ed..f2c74e70c11e 100644 --- a/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx +++ b/apps/judicial-system/web/src/routes/Shared/Statement/Statement.tsx @@ -74,12 +74,12 @@ const Statement = () => { }/${id}` const handleNextButtonClick = useCallback(async () => { - const allSucceeded = await handleUpload( + const uploadResult = await handleUpload( uploadFiles.filter((file) => file.percent === 0), updateUploadFile, ) - if (!allSucceeded) { + if (uploadResult !== 'ALL_SUCCEEDED') { return } diff --git a/apps/judicial-system/web/src/utils/hooks/index.ts b/apps/judicial-system/web/src/utils/hooks/index.ts index cc4edd7be94a..b2b9f733f759 100644 --- a/apps/judicial-system/web/src/utils/hooks/index.ts +++ b/apps/judicial-system/web/src/utils/hooks/index.ts @@ -1,9 +1,12 @@ -export type { UpdateCase } from './useCase' -export { default as useCase, formatDateForServer } from './useCase' +export { + type UpdateCase, + default as useCase, + formatDateForServer, +} from './useCase' export { default as useFileList } from './useFileList' export { default as useInstitution } from './useInstitution' -export type { TUploadFile } from './useS3Upload/useS3Upload' export { + type TUploadFile, useUploadFiles, default as useS3Upload, } from './useS3Upload/useS3Upload' @@ -11,14 +14,17 @@ export { useGetLawyers, useGetLawyer } from './useLawyers/useLawyers' export { default as useDeb } from './useDeb' export { default as useViewport } from './useViewport/useViewport' export { default as useOnceOn } from './useOnceOn' -export type { CaseFileStatus, CaseFileWithStatus } from './useCourtUpload' -export { UploadState, useCourtUpload } from './useCourtUpload' +export { + type CaseFileStatus, + type CaseFileWithStatus, + UploadState, + useCourtUpload, +} from './useCourtUpload' export { getAppealDecision, default as useAppealAlertBanner, } from './useAppealAlertBanner' export { default as useSort } from './useSort/useSort' - export { useGeoLocation } from './useGeoLocation/useGeoLocation' export { default as useDefendants } from './useDefendants' export { diff --git a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts index 3da35cabd7a8..d47a6ad001f6 100644 --- a/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts +++ b/apps/judicial-system/web/src/utils/hooks/useS3Upload/useS3Upload.ts @@ -312,9 +312,17 @@ const useS3Upload = (caseId: string) => { } }) - return Promise.all(promises).then((results) => - results.every((result) => result), - ) + return Promise.all(promises).then((results) => { + if (results.every((result) => result)) { + return 'ALL_SUCCEEDED' + } + + if (results.some((result) => result)) { + return 'SOME_SUCCEEDED' + } + + return 'NONE_SUCCEEDED' + }) }, [getPresignedPost, addFileToCaseState, formatMessage], ) diff --git a/libs/judicial-system/types/src/lib/notification.ts b/libs/judicial-system/types/src/lib/notification.ts index 1860778fdedf..410ef203879e 100644 --- a/libs/judicial-system/types/src/lib/notification.ts +++ b/libs/judicial-system/types/src/lib/notification.ts @@ -18,6 +18,7 @@ export enum NotificationType { INDICTMENT_DENIED = 'INDICTMENT_DENIED', INDICTMENT_RETURNED = 'INDICTMENT_RETURNED', INDICTMENTS_WAITING_FOR_CONFIRMATION = 'INDICTMENTS_WAITING_FOR_CONFIRMATION', + CASE_FILES_UPDATED = 'CASE_FILES_UPDATED', SERVICE_SUCCESSFUL = 'SERVICE_SUCCESSFUL', SERVICE_FAILED = 'SERVICE_FAILED', DEFENDANT_SELECTED_DEFENDER = 'DEFENDANT_SELECTED_DEFENDER',