diff --git a/apps/judicial-system/backend/src/app/messages/notifications.ts b/apps/judicial-system/backend/src/app/messages/notifications.ts index 43bda5af9fb3..ed00262e39d9 100644 --- a/apps/judicial-system/backend/src/app/messages/notifications.ts +++ b/apps/judicial-system/backend/src/app/messages/notifications.ts @@ -856,4 +856,19 @@ export const notifications = { description: 'Texti í pósti til aðila máls þegar ný gögn eru send', }, }), + courtOfficialAssignedEmail: defineMessages({ + subject: { + id: 'judicial.system.backend:notifications.court_official_assigned_email.subject', + defaultMessage: 'Úthlutun máls {courtCaseNumber}', + description: + 'Fyrirsögn í pósti til dómara og dómritara þegar máli er úthlutað á þau', + }, + body: { + id: 'judicial.system.backend:notifications.court_official_assigned_email.body', + defaultMessage: + 'Héraðsdómur hefur skráð þig sem {role, select, DISTRICT_COURT_JUDGE {dómara} DISTRICT_COURT_REGISTRAR {dómritara} other {óþekkt}} í máli {courtCaseNumber}. Hægt er að nálgast gögn málsins á {linkStart}yfirlitssíðu málsins í Réttarvörslugátt{linkEnd}', + description: + 'Texti í pósti til dómara og dómritara þegar máli er úthlutað á þau', + }, + }), } 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 b9980ed191fd..75fe945637d3 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 @@ -687,6 +687,34 @@ export class CaseService { ]) } + private addMessagesForDistrictCourtJudgeAssignedToQueue( + theCase: Case, + user: TUser, + ): Promise { + return this.messageService.sendMessagesToQueue([ + { + type: MessageType.NOTIFICATION, + user, + caseId: theCase.id, + body: { type: CaseNotificationType.DISTRICT_COURT_JUDGE_ASSIGNED }, + }, + ]) + } + + private addMessagesForDistrictCourtRegistrarAssignedToQueue( + theCase: Case, + user: TUser, + ): Promise { + return this.messageService.sendMessagesToQueue([ + { + type: MessageType.NOTIFICATION, + user, + caseId: theCase.id, + body: { type: CaseNotificationType.DISTRICT_COURT_REGISTRAR_ASSIGNED }, + }, + ]) + } + private addMessagesForReceivedCaseToQueue( theCase: Case, user: TUser, @@ -1403,6 +1431,30 @@ export class CaseService { } } + if ( + isIndictment && + [CaseState.SUBMITTED, CaseState.RECEIVED].includes(updatedCase.state) + ) { + const isJudgeChanged = + updatedCase.judge?.nationalId !== theCase.judge?.nationalId + const isRegistrarChanged = + updatedCase.registrar?.nationalId !== theCase.registrar?.nationalId + + if (isJudgeChanged) { + await this.addMessagesForDistrictCourtJudgeAssignedToQueue( + updatedCase, + user, + ) + } + + if (isRegistrarChanged) { + await this.addMessagesForDistrictCourtRegistrarAssignedToQueue( + updatedCase, + user, + ) + } + } + if ( isIndictment && ![ 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 04cccc5de218..567e17620945 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 @@ -28,7 +28,7 @@ export const defenderNotificationRule: RolesRule = { ], } as RolesRule -// Allows district court judges to send notifiications +// Allows district court judges to send notifications export const districtCourtJudgeNotificationRule: RolesRule = { role: UserRole.DISTRICT_COURT_JUDGE, type: RulesType.FIELD_VALUES, diff --git a/apps/judicial-system/backend/src/app/modules/notification/services/caseNotification/caseNotification.service.ts b/apps/judicial-system/backend/src/app/modules/notification/services/caseNotification/caseNotification.service.ts index 0c4cbba076cb..fe2aa576b272 100644 --- a/apps/judicial-system/backend/src/app/modules/notification/services/caseNotification/caseNotification.service.ts +++ b/apps/judicial-system/backend/src/app/modules/notification/services/caseNotification/caseNotification.service.ts @@ -21,6 +21,7 @@ import { INDICTMENTS_OVERVIEW_ROUTE, INVESTIGATION_CASE_POLICE_CONFIRMATION_ROUTE, RESTRICTION_CASE_OVERVIEW_ROUTE, + ROUTE_HANDLER_ROUTE, SIGNED_VERDICT_OVERVIEW_ROUTE, } from '@island.is/judicial-system/consts' import { @@ -46,6 +47,7 @@ import { RequestSharedWithDefender, SessionArrangements, type User, + UserRole, } from '@island.is/judicial-system/types' import { @@ -689,6 +691,28 @@ export class CaseNotificationService extends BaseNotificationService { }) } + private sendCourtOfficialAssignedEmailNotificationForIndictmentCase( + theCase: Case, + role: UserRole.DISTRICT_COURT_JUDGE | UserRole.DISTRICT_COURT_REGISTRAR, + ): Promise { + const official = + role === UserRole.DISTRICT_COURT_JUDGE ? theCase.judge : theCase.registrar + + return this.sendEmail( + this.formatMessage(notifications.courtOfficialAssignedEmail.subject, { + courtCaseNumber: theCase.courtCaseNumber, + }), + this.formatMessage(notifications.courtOfficialAssignedEmail.body, { + courtCaseNumber: theCase.courtCaseNumber, + role, + linkStart: ``, + linkEnd: '', + }), + official?.name, + official?.email, + ) + } + private sendCourtDateEmailNotificationForIndictmentCase( theCase: Case, user: User, @@ -810,6 +834,25 @@ export class CaseNotificationService extends BaseNotificationService { return result } + + private async sendDistrictCourtUserAssignedNotifications( + theCase: Case, + userRole: UserRole.DISTRICT_COURT_JUDGE | UserRole.DISTRICT_COURT_REGISTRAR, + ): Promise { + const recipient = + await this.sendCourtOfficialAssignedEmailNotificationForIndictmentCase( + theCase, + userRole, + ) + + return await this.recordNotification( + theCase.id, + userRole === UserRole.DISTRICT_COURT_JUDGE + ? CaseNotificationType.DISTRICT_COURT_JUDGE_ASSIGNED + : CaseNotificationType.DISTRICT_COURT_REGISTRAR_ASSIGNED, + [recipient], + ) + } //#endregion //#region RULING notifications @@ -2552,6 +2595,16 @@ export class CaseNotificationService extends BaseNotificationService { return this.sendReceivedByCourtNotifications(theCase) case CaseNotificationType.COURT_DATE: return this.sendCourtDateNotifications(theCase, user) + case CaseNotificationType.DISTRICT_COURT_JUDGE_ASSIGNED: + return this.sendDistrictCourtUserAssignedNotifications( + theCase, + UserRole.DISTRICT_COURT_JUDGE, + ) + case CaseNotificationType.DISTRICT_COURT_REGISTRAR_ASSIGNED: + return this.sendDistrictCourtUserAssignedNotifications( + theCase, + UserRole.DISTRICT_COURT_REGISTRAR, + ) case CaseNotificationType.RULING: return this.sendRulingNotifications(theCase) case CaseNotificationType.MODIFIED: diff --git a/apps/services/bff/src/app/modules/user/user.controller.spec.ts b/apps/services/bff/src/app/modules/user/user.controller.spec.ts index dba1a6918e0a..a6dfc9d72ef6 100644 --- a/apps/services/bff/src/app/modules/user/user.controller.spec.ts +++ b/apps/services/bff/src/app/modules/user/user.controller.spec.ts @@ -230,5 +230,117 @@ describe('UserController', () => { profile: expiredTokenResponse.userProfile, }) }) + + it('should not refresh token when token exists but is not expired', async () => { + // Arrange - Set up login attempt in cache + mockCacheStore.set( + `attempt::${mockConfig.name}::${SID_VALUE}`, + createLoginAttempt(mockConfig), + ) + + // Initialize session + await server.get('/login') + await server + .get('/callbacks/login') + .set('Cookie', [`${SESSION_COOKIE_NAME}=${SID_VALUE}`]) + .query({ code: 'some_code', state: SID_VALUE }) + + // Set valid (not expired) token in cache + const validTokenResponse = { + ...mockCachedTokenResponse, + accessTokenExp: Date.now() + 1000, // Future expiration + } + mockCacheStore.set( + `current::${mockConfig.name}::${SID_VALUE}`, + validTokenResponse, + ) + + // Act + const res = await server + .get('/user') + .query({ refresh: 'true' }) + .set('Cookie', [`${SESSION_COOKIE_NAME}=${SID_VALUE}`]) + + // Assert + expect(mockTokenRefreshService.refreshToken).not.toHaveBeenCalled() + expect(res.status).toEqual(HttpStatus.OK) + expect(res.body).toEqual({ + scopes: validTokenResponse.scopes, + profile: validTokenResponse.userProfile, + }) + }) + + it('should refresh token only when all conditions are met (token exists, is expired, and refresh=true)', async () => { + // Arrange - Set up login attempt in cache + mockCacheStore.set( + `attempt::${mockConfig.name}::${SID_VALUE}`, + createLoginAttempt(mockConfig), + ) + + const testCases = [ + { + exists: true, + expired: true, + refresh: true, + shouldCallRefresh: true, + }, + { + exists: true, + expired: true, + refresh: false, + shouldCallRefresh: false, + }, + { + exists: true, + expired: false, + refresh: true, + shouldCallRefresh: false, + }, + { + exists: false, + expired: true, + refresh: true, + shouldCallRefresh: false, + }, + ] + + for (const testCase of testCases) { + // Reset mocks + jest.clearAllMocks() + mockCacheStore.clear() + + if (testCase.exists) { + const tokenResponse = { + ...mockCachedTokenResponse, + accessTokenExp: testCase.expired + ? Date.now() - 1000 // Expired + : Date.now() + 1000, // Not expired + } + mockCacheStore.set( + `current::${mockConfig.name}::${SID_VALUE}`, + tokenResponse, + ) + } + + // Act + const res = await server + .get('/user') + .query({ refresh: testCase.refresh.toString() }) + .set('Cookie', [`${SESSION_COOKIE_NAME}=${SID_VALUE}`]) + + // Assert + if (testCase.shouldCallRefresh) { + expect(mockTokenRefreshService.refreshToken).toHaveBeenCalled() + } else { + expect(mockTokenRefreshService.refreshToken).not.toHaveBeenCalled() + } + + if (testCase.exists) { + expect(res.status).toEqual(HttpStatus.OK) + } else { + expect(res.status).toEqual(HttpStatus.UNAUTHORIZED) + } + } + }) }) }) diff --git a/apps/services/bff/src/app/modules/user/user.service.ts b/apps/services/bff/src/app/modules/user/user.service.ts index 4babb4df7a4e..74f576a5d8aa 100644 --- a/apps/services/bff/src/app/modules/user/user.service.ts +++ b/apps/services/bff/src/app/modules/user/user.service.ts @@ -5,6 +5,7 @@ import { BffUser } from '@island.is/shared/types' import { SESSION_COOKIE_NAME } from '../../constants/cookies' import { ErrorService } from '../../services/error.service' +import { hasTimestampExpiredInMS } from '../../utils/has-timestamp-expired-in-ms' import { CachedTokenResponse } from '../auth/auth.types' import { TokenRefreshService } from '../auth/token-refresh.service' import { CacheService } from '../cache/cache.service' @@ -58,7 +59,11 @@ export class UserService { false, ) - if (cachedTokenResponse && refresh) { + if ( + cachedTokenResponse && + hasTimestampExpiredInMS(cachedTokenResponse.accessTokenExp) && + refresh + ) { cachedTokenResponse = await this.tokenRefreshService.refreshToken({ sid, encryptedRefreshToken: cachedTokenResponse.encryptedRefreshToken, diff --git a/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.css.ts b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.css.ts index 66a268028923..8550d211f92b 100644 --- a/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.css.ts +++ b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.css.ts @@ -46,3 +46,7 @@ export const yearSelectContainer = style({ md: { width: '204px' }, }), }) + +export const noWrap = style({ + flexWrap: 'nowrap', +}) diff --git a/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.tsx b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.tsx index 143a47f3b0de..2dfab0c78d4e 100644 --- a/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.tsx +++ b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculator.tsx @@ -50,6 +50,7 @@ import { GET_ORGANIZATION_PAGE_QUERY, GET_ORGANIZATION_QUERY, } from '../../queries' +import { PensionCalculatorTitle } from './PensionCalculatorTitle' import { PensionCalculatorWrapper } from './PensionCalculatorWrapper' import { translationStrings } from './translationStrings' import { @@ -554,16 +555,12 @@ const PensionCalculator: CustomScreen = ({ > - {isNewSystemActive && ( - - {title}
{titlePostfix}
-
- )} - {!isNewSystemActive && ( - - {title} {titlePostfix} - - )} + {formatMessage(translationStrings.isTurnedOff)}
@@ -584,16 +581,12 @@ const PensionCalculator: CustomScreen = ({ - {isNewSystemActive && ( - - {title}
{titlePostfix}
-
- )} - {!isNewSystemActive && ( - - {title} {titlePostfix} - - )} +
= ({ > - {isNewSystemActive && ( - - {title}
{titlePostfix}
-
- )} - {!isNewSystemActive && ( - - {title} {titlePostfix} - - )} + {formatMessage(translationStrings.isTurnedOff)} @@ -391,16 +388,12 @@ const PensionCalculatorResults: CustomScreen = ({ - {isNewSystemActive && ( - - {title}
{titlePostfix}
-
- )} - {!isNewSystemActive && ( - - {title} {titlePostfix} - - )} + {formatMessage( diff --git a/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculatorTitle.tsx b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculatorTitle.tsx new file mode 100644 index 000000000000..4a61eb3b61fb --- /dev/null +++ b/apps/web/screens/Organization/SocialInsuranceAdministration/PensionCalculatorTitle.tsx @@ -0,0 +1,45 @@ +import { useIntl } from 'react-intl' + +import { GridColumn, GridRow, Text } from '@island.is/island-ui/core' + +import { translationStrings } from './translationStrings' +import * as styles from './PensionCalculator.css' + +interface PensionCalculatorTitleProps { + isNewSystemActive: boolean + title: string + titlePostfix: string + titleVariant: 'h1' | 'h2' +} + +export const PensionCalculatorTitle = ({ + isNewSystemActive, + title, + titlePostfix, + titleVariant, +}: PensionCalculatorTitleProps) => { + const { formatMessage } = useIntl() + if (isNewSystemActive) + return ( + + + + + + + {title}
{titlePostfix}
+
+
+
+ ) + return ( + + {title} {titlePostfix} + + ) +} diff --git a/apps/web/screens/Organization/SocialInsuranceAdministration/translationStrings.ts b/apps/web/screens/Organization/SocialInsuranceAdministration/translationStrings.ts index 8a273868f14b..89f5b13b9202 100644 --- a/apps/web/screens/Organization/SocialInsuranceAdministration/translationStrings.ts +++ b/apps/web/screens/Organization/SocialInsuranceAdministration/translationStrings.ts @@ -625,6 +625,13 @@ export const translationStrings = defineMessages({ defaultMessage: 'Eftir 1. september 2025', description: 'Eftir 1. september 2025', }, + after1stSeptember2025IconUrl: { + id: 'web.pensionCalculator:after1stSeptember2025IconUrl', + defaultMessage: + 'https://images.ctfassets.net/8k0h54kbe6bj/5RIwKVet87Nm4ycltkzjnX/9c594855a9b2f90dde63766ee87a09ca/58dd40fbf365769d984be22a9b64bc29.png', + description: + 'Mynd vinstra megin við titil "Reiknivél örorku- og endurhæfingargreiðslna eftir 1. september 2025"', + }, after1stSeptember2025Calculate: { id: 'web.pensionCalculator:after1stSeptember2025Calculate', defaultMessage: 'Reikna', diff --git a/libs/application/template-api-modules/src/lib/modules/templates/new-primary-school/new-primary-school.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/new-primary-school/new-primary-school.service.ts index 80ce338d5cd2..15b415938eec 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/new-primary-school/new-primary-school.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/new-primary-school/new-primary-school.service.ts @@ -13,6 +13,7 @@ import { TemplateApiModuleActionProps } from '../../../types' import { BaseTemplateApiService } from '../../base-template-api.service' import { transformApplicationToNewPrimarySchoolDTO } from './new-primary-school.utils' import { isRunningOnEnvironment } from '@island.is/shared/utils' +import { isRunningInProduction } from '../parental-leave/constants' @Injectable() export class NewPrimarySchoolService extends BaseTemplateApiService { @@ -38,6 +39,66 @@ export class NewPrimarySchoolService extends BaseTemplateApiService { } async getChildren({ auth }: TemplateApiModuleActionProps) { + if (!isRunningInProduction) { + if (auth.nationalId === '0101303019') { + return [ + { + nationalId: '1111111119', + fullName: 'Stubbur Maack', + genderCode: '3', + livesWithApplicant: true, + livesWithBothParents: true, + }, + ] + } + if (auth.nationalId === '0101302989') { + return [ + { + nationalId: '2222222229', + fullName: 'Stúfur Maack ', + genderCode: '3', + livesWithApplicant: true, + livesWithBothParents: true, + otherParent: { + nationalId: '0101302399', + fullName: 'Gervimaður Færeyjar', + address: { + streetName: 'Hvassaleiti 5', + postalCode: '103', + city: 'Reykjavík', + municipalityCode: '0000', + }, + genderCode: '2', + }, + }, + { + nationalId: '5555555559', + fullName: 'Bína Maack ', + genderCode: '4', + livesWithApplicant: true, + livesWithBothParents: true, + }, + { + nationalId: '6666666669', + fullName: 'Snúður Maack', + genderCode: '3', + livesWithApplicant: true, + livesWithBothParents: true, + }, + ] + } + if (auth.nationalId === '0101304929') { + return [ + { + nationalId: '6666666669', + fullName: 'Snúður Maack', + genderCode: '3', + livesWithApplicant: true, + livesWithBothParents: true, + }, + ] + } + } const children = await this.nationalRegistryService.getChildrenCustodyInformation(auth) diff --git a/libs/application/templates/reference-template/src/lib/dataSchema.ts b/libs/application/templates/reference-template/src/lib/dataSchema.ts index faa3b728cff4..e4945ef27981 100644 --- a/libs/application/templates/reference-template/src/lib/dataSchema.ts +++ b/libs/application/templates/reference-template/src/lib/dataSchema.ts @@ -15,6 +15,20 @@ const careerHistoryCompaniesValidation = (data: any) => { } export const ExampleSchema = z.object({ approveExternalData: z.boolean().refine((v) => v), + tableRepeaterField: z.array( + z.object({ + nationalIdWithName: z.object({ + name: z.string().min(1).max(256), + nationalId: z.string().refine((n) => n && kennitala.isValid(n), { + params: m.dataSchemeNationalId, + }), + phone: z.string().refine(isValidNumber, { + params: m.dataSchemePhoneNumber, + }), + email: z.string().email(), + }), + }), + ), person: z.object({ name: z.string().min(1).max(256), age: z.string().refine((x) => { diff --git a/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx b/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx index 9a420ccf1e56..4dbb1ffe6f0c 100644 --- a/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx +++ b/libs/application/templates/social-insurance-administration/income-plan/src/fields/Review/index.tsx @@ -126,21 +126,6 @@ export const Review: FC = ({
- {state === `${States.TRYGGINGASTOFNUN_SUBMITTED}` && ( - - )}