From 149434332f2a15eebd706edbf9f7639e21e98917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3r=C3=B0ur=20H?= Date: Fri, 27 Sep 2024 10:47:49 +0000 Subject: [PATCH 01/18] chore(regulations-admin): Update presigned url for file uploader (#16106) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../admin/regulations-admin/src/utils/fileUploader.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/libs/portals/admin/regulations-admin/src/utils/fileUploader.ts b/libs/portals/admin/regulations-admin/src/utils/fileUploader.ts index c4559ddaf114..acd0e4f6b217 100644 --- a/libs/portals/admin/regulations-admin/src/utils/fileUploader.ts +++ b/libs/portals/admin/regulations-admin/src/utils/fileUploader.ts @@ -1,14 +1,10 @@ import { EditorFileUploader } from '@island.is/regulations-tools/Editor' import { RegulationDraftId } from '@island.is/regulations/admin' import { fileUrl, useS3Upload } from './dataHooks' -import { isRunningOnEnvironment } from '@island.is/shared/utils' export function useFileUploader(draftId: RegulationDraftId) { const { createPresignedPost, createFormData } = useS3Upload() - const isDevelopment = - isRunningOnEnvironment('dev') || isRunningOnEnvironment('local') - const fileUploader = (): EditorFileUploader => async (blobInfo, success, failure, progress) => { try { @@ -47,11 +43,7 @@ export function useFileUploader(draftId: RegulationDraftId) { // Create FormData and send the request const formData = createFormData(presignedPost, blob as File) - request.open( - 'POST', - `${isDevelopment ? presignedPost?.url : fileUrl + '/'}`, - true, - ) + request.open('POST', presignedPost?.url, true) request.send(formData) } catch (error) { console.error('Error during upload:', error) From 5efe4fddc4e7fb3de68e8ee7cefbb5535e4087dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 27 Sep 2024 13:12:14 +0200 Subject: [PATCH 02/18] chore(j-s): Indictment Case Arrignment Date (#16156) * Locks subpoena fields when arraignment date has been set * Move arraignment date message handling to the server side * Updates tests and fixes date comparison --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/app/modules/case/case.service.ts | 46 +++++++++++++++--- .../case/test/caseController/update.spec.ts | 48 ++++++++++++++++++- .../notification/notification.service.ts | 9 ---- .../app/modules/subpoena/subpoena.service.ts | 1 + .../Court/Indictments/Subpoena/Subpoena.tsx | 24 +--------- 5 files changed, 87 insertions(+), 41 deletions(-) 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 60896a1422b8..65ad33c4d2ee 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 @@ -1146,15 +1146,29 @@ export class CaseService { private addMessagesForNewCourtDateToQueue( theCase: Case, user: TUser, + arraignmentDateChanged: boolean, ): Promise { - return this.messageService.sendMessagesToQueue([ + const messages: Message[] = [ { type: MessageType.NOTIFICATION, user, caseId: theCase.id, body: { type: NotificationType.COURT_DATE }, }, - ]) + ] + + if (arraignmentDateChanged) { + theCase.defendants?.forEach((defendant) => { + messages.push({ + type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, + user, + caseId: theCase.id, + elementId: defendant.id, + }) + }) + } + + return this.messageService.sendMessagesToQueue(messages) } private async addMessagesForUpdatedCaseToQueue( @@ -1313,11 +1327,29 @@ export class CaseService { } // This only applies to indictments - const courtDate = DateLog.courtDate(theCase.dateLogs) - const updatedCourtDate = DateLog.courtDate(updatedCase.dateLogs) - if (updatedCourtDate && updatedCourtDate.date !== courtDate?.date) { - // New court date - await this.addMessagesForNewCourtDateToQueue(updatedCase, user) + if (isIndictment) { + const arraignmentDate = DateLog.arraignmentDate(theCase.dateLogs) + const updatedArraignmentDate = DateLog.arraignmentDate( + updatedCase.dateLogs, + ) + const arraignmentDateChanged = + updatedArraignmentDate && + updatedArraignmentDate.date.getTime() !== + arraignmentDate?.date.getTime() + const courtDate = DateLog.courtDate(theCase.dateLogs) + const updatedCourtDate = DateLog.courtDate(updatedCase.dateLogs) + const courtDateChanged = + updatedCourtDate && + updatedCourtDate.date.getTime() !== courtDate?.date.getTime() + + if (arraignmentDateChanged || courtDateChanged) { + // New arraignment date or new court date + await this.addMessagesForNewCourtDateToQueue( + updatedCase, + user, + Boolean(arraignmentDateChanged), + ) + } } } 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 0ffde6ad16ca..932319cac89e 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 @@ -872,11 +872,56 @@ describe('CaseController - Update', () => { }) }) - describe('court date updated', () => { + describe('indictment arraignment date updated', () => { + const arraignmentDate = { date: new Date(), location: uuid() } + const caseToUpdate = { arraignmentDate } + const updatedCase = { + ...theCase, + type: CaseType.INDICTMENT, + dateLogs: [{ dateType: DateType.ARRAIGNMENT_DATE, ...arraignmentDate }], + } + + beforeEach(async () => { + const mockFindOne = mockCaseModel.findOne as jest.Mock + mockFindOne.mockResolvedValueOnce(updatedCase) + + await givenWhenThen(caseId, user, theCase, caseToUpdate) + }) + + it('should update case', () => { + expect(mockDateLogModel.create).toHaveBeenCalledWith( + { dateType: DateType.ARRAIGNMENT_DATE, caseId, ...arraignmentDate }, + { transaction }, + ) + expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ + { + type: MessageType.NOTIFICATION, + user, + caseId, + body: { type: NotificationType.COURT_DATE }, + }, + { + type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, + user, + caseId: theCase.id, + elementId: defendantId1, + }, + { + type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, + user, + caseId: theCase.id, + elementId: defendantId2, + }, + ]) + }) + }) + + describe('indictment court date updated', () => { const courtDate = { date: new Date(), location: uuid() } const caseToUpdate = { courtDate } const updatedCase = { ...theCase, + type: CaseType.INDICTMENT, dateLogs: [{ dateType: DateType.COURT_DATE, ...courtDate }], } @@ -892,7 +937,6 @@ describe('CaseController - Update', () => { { dateType: DateType.COURT_DATE, caseId, ...courtDate }, { transaction }, ) - expect(mockMessageService.sendMessagesToQueue).toHaveBeenCalledWith([ { type: MessageType.NOTIFICATION, 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 e4ffc288777d..8103b1106f14 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 @@ -69,15 +69,6 @@ export class NotificationService { ] } else { messages = [this.getNotificationMessage(type, user, theCase)] - theCase.defendants?.forEach((defendant) => { - // TODO: move this elsewhere when we know exactly where the trigger should be - messages.push({ - type: MessageType.DELIVERY_TO_POLICE_SUBPOENA, - user, - caseId: theCase.id, - elementId: defendant.id, - }) - }) } break case NotificationType.HEADS_UP: 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 62d0355fd57c..c28cf4ec4875 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 @@ -126,6 +126,7 @@ export class SubpoenaService { return { delivered: false } } + // TODO: Improve error handling by checking how many rows were affected and posting error event await this.subpoenaModel.update( { subpoenaId: createdSubpoena.subpoenaId }, { where: { id: subpoena.id } }, diff --git a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx index 4d13fb753f1c..11c767d7008f 100644 --- a/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx +++ b/apps/judicial-system/web/src/routes/Court/Indictments/Subpoena/Subpoena.tsx @@ -19,14 +19,9 @@ import { SectionHeading, useCourtArrangements, } from '@island.is/judicial-system-web/src/components' -import { NotificationType } from '@island.is/judicial-system-web/src/graphql/schema' import { SubpoenaType } from '@island.is/judicial-system-web/src/routes/Court/components' import type { stepValidationsType } from '@island.is/judicial-system-web/src/utils/formHelper' -import { - useCase, - useDefendants, -} from '@island.is/judicial-system-web/src/utils/hooks' -import { hasSentNotification } from '@island.is/judicial-system-web/src/utils/stepHelper' +import { useDefendants } from '@island.is/judicial-system-web/src/utils/hooks' import { isSubpoenaStepValid } from '@island.is/judicial-system-web/src/utils/validate' import { subpoena as strings } from './Subpoena.strings' @@ -39,12 +34,10 @@ const Subpoena: FC = () => { const { formatMessage } = useIntl() const { courtDate, - courtDateHasChanged, handleCourtDateChange, handleCourtRoomChange, sendCourtDateToServer, } = useCourtArrangements(workingCase, setWorkingCase, 'arraignmentDate') - const { sendNotification } = useCase() const isArraignmentScheduled = Boolean(workingCase.arraignmentDate) @@ -69,18 +62,6 @@ const Subpoena: FC = () => { }) } - if ( - !hasSentNotification( - NotificationType.COURT_DATE, - workingCase.notifications, - ).hasSent || - courtDateHasChanged - ) { - promises.push( - sendNotification(workingCase.id, NotificationType.COURT_DATE), - ) - } - const allDataSentToServer = await Promise.all(promises) if (!allDataSentToServer.every((result) => result)) { return @@ -92,11 +73,8 @@ const Subpoena: FC = () => { isArraignmentScheduled, sendCourtDateToServer, workingCase.defendants, - workingCase.notifications, workingCase.id, - courtDateHasChanged, updateDefendant, - sendNotification, ], ) From a8b2817ba215f09ca9c2f472b2be8073d3052db8 Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:48:52 +0100 Subject: [PATCH 03/18] feat(ids-api): Add feature flag for general mandate delegation type. (#16182) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add feature flag for general mandate delegation type. * Flip featureFlagService.getValue to be default false --------- Co-authored-by: Sævar Már Atlason Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../delegations-incoming.service.ts | 22 +++++++++++++------ libs/feature-flags/src/lib/features.ts | 3 +++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts index 56355428a63e..ca782439de6c 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts @@ -189,13 +189,21 @@ export class DelegationsIncomingService { } if (types?.includes(AuthDelegationType.GeneralMandate)) { - delegationPromises.push( - this.delegationsIncomingCustomService.findAllAvailableGeneralMandate( + const isGeneralMandateDelegationEnabled = + await this.featureFlagService.getValue( + Features.isGeneralMandateDelegationEnabled, + false, user, - clientAllowedApiScopes, - client.requireApiScopes, - ), - ) + ) + if (isGeneralMandateDelegationEnabled) { + delegationPromises.push( + this.delegationsIncomingCustomService.findAllAvailableGeneralMandate( + user, + clientAllowedApiScopes, + client.requireApiScopes, + ), + ) + } } if ( @@ -220,7 +228,7 @@ export class DelegationsIncomingService { const isLegalRepresentativeDelegationEnabled = await this.featureFlagService.getValue( Features.isLegalRepresentativeDelegationEnabled, - true, + false, user, ) if (isLegalRepresentativeDelegationEnabled) { diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts index eb5bde4c7e7d..fd67ac6db300 100644 --- a/libs/feature-flags/src/lib/features.ts +++ b/libs/feature-flags/src/lib/features.ts @@ -107,6 +107,9 @@ export enum Features { // Legal represantative delegation type isLegalRepresentativeDelegationEnabled = 'isLegalRepresentativeDelegationEnabled', + + // General mandate delegation type + isGeneralMandateDelegationEnabled = 'isGeneralMandateDelegationEnabled', } export enum ServerSideFeature { From 6d4e0306d8903c3e07d98fe398168c787b3be261 Mon Sep 17 00:00:00 2001 From: mannipje <135017126+mannipje@users.noreply.github.com> Date: Fri, 27 Sep 2024 13:52:18 +0000 Subject: [PATCH 04/18] feat(web): Add default header for HSA organization (#16131) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Wrapper/OrganizationWrapper.css.ts | 23 +++++++++++++++++++ .../Wrapper/OrganizationWrapper.tsx | 11 ++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.css.ts b/apps/web/components/Organization/Wrapper/OrganizationWrapper.css.ts index 5ccd9797f6b2..13d010320762 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.css.ts +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.css.ts @@ -105,6 +105,29 @@ export const rikissaksoknariHeaderGridContainerWidth = style([ export const rikissaksoknariHeaderGridContainerSubpage = rikissaksoknariHeaderGridContainerBase +export const hsaHeaderGridContainerBase = style({ + display: 'grid', + maxWidth: '1342px', + margin: '0 auto', + ...themeUtils.responsiveStyle({ + lg: { + gridTemplateRows: '315px', + gridTemplateColumns: '65fr 35fr', + }, + }), +}) + +export const hsaHeaderGridContainerWidthSubpage = hsaHeaderGridContainerBase + +export const hsaHeaderGridContainerWidth = style([ + hsaHeaderGridContainerBase, + themeUtils.responsiveStyle({ + lg: { + background: `url('https://images.ctfassets.net/8k0h54kbe6bj/uc45ywvPOYsIUEQTNfE6s/72fd0f2229407e18c6e2908fb13f51c3/Header_HSA.png') no-repeat right bottom,linear-gradient(90deg, #CCDFFF 0%, #F6F6F6 84.85%)`, + }, + }), +]) + export const rikislogmadurHeaderGridContainerWidthBase = style({ display: 'grid', maxWidth: '1342px', diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index af00b7732f18..56ce9ff733b2 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -478,7 +478,16 @@ export const OrganizationHeader: React.FC< /> ) case 'hsa': - return ( + return n('usingDefaultHeader', false) ? ( + + ) : ( Date: Fri, 27 Sep 2024 14:02:35 +0000 Subject: [PATCH 05/18] fix(application-aod): Validation needed for dateofbirth (#16075) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../announcement-of-death/src/lib/dataSchema.ts | 15 +++++++++++++++ .../announcement-of-death/src/lib/messages.ts | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts b/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts index e1feabcf8435..14f4d0db21e7 100644 --- a/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts +++ b/libs/application/templates/announcement-of-death/src/lib/dataSchema.ts @@ -118,6 +118,21 @@ export const dataSchema = z.object({ dateOfBirth: z.string().min(1).optional(), dummy: z.boolean().optional(), }) + .refine( + ({ name, relation, nationalId, foreignCitizenship, dateOfBirth }) => { + const hasNameAndRelation = name && relation + + if (foreignCitizenship && foreignCitizenship.length !== 0) { + return Boolean(dateOfBirth) && hasNameAndRelation + } else { + return Boolean(nationalId) && hasNameAndRelation + } + }, + { + message: m.errorNoDateOfBirthProvided.defaultMessage, + path: ['dateOfBirth'], + }, + ) .array() .optional(), encountered: z.boolean().optional(), diff --git a/libs/application/templates/announcement-of-death/src/lib/messages.ts b/libs/application/templates/announcement-of-death/src/lib/messages.ts index db3556c25385..ebbd3a17613f 100644 --- a/libs/application/templates/announcement-of-death/src/lib/messages.ts +++ b/libs/application/templates/announcement-of-death/src/lib/messages.ts @@ -558,6 +558,11 @@ Ef ekkert á við sem hér að ofan er talið rennur arfur í ríkissjóð. Nán defaultMessage: 'Númer má ekki vera tómt', description: 'Invalid general asset number error message', }, + errorNoDateOfBirthProvided: { + id: 'aod.application:error.errorNoDateOfBirthProvided', + defaultMessage: 'Fæðingardagur þarf að vera fylltur út', + description: 'Date of birth is required', + }, /* Announcement */ announcementTitle: { From 9095b025afd2925ad29b4023354293b6d51e6d8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 27 Sep 2024 14:35:02 +0000 Subject: [PATCH 06/18] fix(search-indexer): Limit what fields are indexed for "Latest Generic List Items" model (#16187) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../lib/models/latestGenericListItems.model.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/libs/cms/src/lib/models/latestGenericListItems.model.ts b/libs/cms/src/lib/models/latestGenericListItems.model.ts index 351712ccbbc6..3fe7a7a43103 100644 --- a/libs/cms/src/lib/models/latestGenericListItems.model.ts +++ b/libs/cms/src/lib/models/latestGenericListItems.model.ts @@ -44,13 +44,18 @@ const mapSeeMorePage = (seeMorePage: IOrganizationSubpage | undefined) => { return mapOrganizationSubpage({ ...seeMorePage, fields: { - ...seeMorePage.fields, + title: seeMorePage.fields?.title, + slug: seeMorePage.fields?.slug, organizationPage: { - ...seeMorePage.fields.organizationPage, + ...seeMorePage.fields?.organizationPage, fields: { - ...seeMorePage.fields.organizationPage?.fields, - slices: [], - bottomSlices: [], + title: seeMorePage.fields?.organizationPage?.fields?.title, + slug: seeMorePage.fields?.organizationPage?.fields?.slug, + featuredImage: + seeMorePage.fields?.organizationPage?.fields?.featuredImage, + organization: + seeMorePage.fields?.organizationPage?.fields?.organization, + theme: seeMorePage.fields?.organizationPage?.fields?.theme, }, }, }, From dc0e0feca551c35749546f2f48807472ad5eec10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rey=20J=C3=B3na?= Date: Fri, 27 Sep 2024 15:09:15 +0000 Subject: [PATCH 07/18] fix(native-app): don't show progress bar for applications with type in progress (#16158) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../applications/components/applications-preview.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/apps/native/app/src/screens/applications/components/applications-preview.tsx b/apps/native/app/src/screens/applications/components/applications-preview.tsx index 38d3948e7ca3..d9bcbcfd5c55 100644 --- a/apps/native/app/src/screens/applications/components/applications-preview.tsx +++ b/apps/native/app/src/screens/applications/components/applications-preview.tsx @@ -68,11 +68,15 @@ export const ApplicationsPreview = ({ /> } progress={ - type !== 'incomplete' - ? undefined - : application.actionCard?.draftFinishedSteps ?? 0 + type === 'incomplete' + ? application.actionCard?.draftFinishedSteps ?? 0 + : undefined + } + progressTotalSteps={ + type === 'incomplete' + ? application.actionCard?.draftTotalSteps ?? 0 + : undefined } - progressTotalSteps={application.actionCard?.draftTotalSteps ?? 0} progressMessage={intl.formatMessage( { id: 'applicationStatusCard.draftProgress', From 3df11faef0fce9c81ecd7e6467381b6a89fa8d94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:48:21 +0000 Subject: [PATCH 08/18] fix(search-indexer): Reduce how much we write and read from elasticsearch (#16189) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/cms/src/lib/search/contentful.service.ts | 2 +- libs/content-search-toolkit/src/services/elastic.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/cms/src/lib/search/contentful.service.ts b/libs/cms/src/lib/search/contentful.service.ts index 8b14f7ac5f4c..52c92df3645a 100644 --- a/libs/cms/src/lib/search/contentful.service.ts +++ b/libs/cms/src/lib/search/contentful.service.ts @@ -475,7 +475,7 @@ export class ContentfulService { let idsChunk = idsCopy.splice(-MAX_REQUEST_COUNT, MAX_REQUEST_COUNT) while (idsChunk.length > 0) { - const size = 1000 + const size = 100 let page = 1 const items: string[] = [] diff --git a/libs/content-search-toolkit/src/services/elastic.service.ts b/libs/content-search-toolkit/src/services/elastic.service.ts index 3799eabdd04c..93f0fdf9436a 100644 --- a/libs/content-search-toolkit/src/services/elastic.service.ts +++ b/libs/content-search-toolkit/src/services/elastic.service.ts @@ -112,10 +112,10 @@ export class ElasticService { refresh = false, ) { try { - // elasticsearch does not like big requests (above 5mb) so we limit the size to X entries just in case const client = await this.getClient() - let requestChunk = getValidBulkRequestChunk(requests) + // elasticsearch does not like big requests (above 5mb) so we limit the size to X entries just in case + let requestChunk = getValidBulkRequestChunk(requests, 10) while (requestChunk.length) { // wait for request b4 continuing From 8fb3ddf24055e209a65ccc94ce22789a398ce8a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Albert?= Date: Fri, 27 Sep 2024 16:13:42 +0000 Subject: [PATCH 09/18] chore(driving-license): minor text changes (#16186) * wip * wip * revert * revert --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../forms/prerequisites/sectionDigitalLicenseInfo.ts | 7 +++++++ .../templates/driving-license/src/lib/messages.ts | 12 +++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts b/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts index 279e17edd222..af680c75640c 100644 --- a/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts +++ b/libs/application/templates/driving-license/src/forms/prerequisites/sectionDigitalLicenseInfo.ts @@ -1,5 +1,6 @@ import { buildAlertMessageField, + buildDescriptionField, buildMultiField, buildSubSection, } from '@island.is/application/core' @@ -24,6 +25,12 @@ export const sectionDigitalLicenseInfo = buildSubSection({ : m.digitalLicenseInfoAlertMessageBFull, alertType: 'info', }), + buildDescriptionField({ + id: 'extraInfo', + title: '', + marginTop: 2, + description: m.digitalLicenseInfoAlertMessageExtraInfo, + }), ], }), ], diff --git a/libs/application/templates/driving-license/src/lib/messages.ts b/libs/application/templates/driving-license/src/lib/messages.ts index 41f59615efb6..fdda81aaa35d 100644 --- a/libs/application/templates/driving-license/src/lib/messages.ts +++ b/libs/application/templates/driving-license/src/lib/messages.ts @@ -122,18 +122,18 @@ export const m = defineMessages({ }, healthDeclarationSectionTitle: { id: 'dl.application:healthDeclarationSection.title', - defaultMessage: 'Heilbrigðisyfirlýsing', + defaultMessage: 'Læknisvottorð', description: 'Health declaration', }, healthDeclarationMultiFieldTitle: { id: 'dl.application:healthDeclarationMultiField.title', - defaultMessage: 'Heilbrigðisyfirlýsing', + defaultMessage: 'Læknisvottorð', description: 'Health declaration', }, healthDeclarationMultiField65Description: { id: 'dl.application:healthDeclarationMultiField65Description#markdown', defaultMessage: - 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nunc nec ultricies ultricies, nunc nisl ultricies nunc, nec ultricies nunc nisl nec nunc. Nullam auctor, nunc nec ultricies ultricies, nunc nisl ultricies nunc, nec ultricies nunc nisl nec nunc.', + 'Þú þarft að skila inn læknisvottorði vegna ökuleyfis til að endurnýja ökuskírteini þitt. Læknisvottorðið þarf að vera frá **heimilislækni** og vegna ökuleyfis. Þegar búið er að ljúka umsókn þarf að skila inn læknisvottorði á valið sýslumannsembætti til að hægt sé að panta skírteinið. **Athugið að skírteinið verður ekki pantað fyrr en búið er að skila inn vottorði.**', description: 'Health declaration', }, healthDeclarationMultiFieldSubTitle: { @@ -486,6 +486,12 @@ export const m = defineMessages({ 'Þú ert að sækja um fullnaðarökuskírteini. Ökuskírteini þitt verður núna einungis gefið út sem stafrænt ökuskírteini og verður aðgengilegt fyrir þig þegar þú hefur lokið þessari pöntun um fullnaðarökuskírteini. Fullnaðarökuskírteini þitt verður framleitt í plasti í byrjun febrúar 2025 og sent til þín með Póstinum, á skráð lögheimili þitt um leið og plastökuskírteinið er tilbúið.', description: 'Digital driving license', }, + digitalLicenseInfoAlertMessageExtraInfo: { + id: 'dl.application:digitalLicenseInfoAlertMessageExtraInfo#markdown', + defaultMessage: + 'Upplýsingar um stafrænt ökuskírteini, hvernig þú sækir það og hleður því í símannn þinn eru aðgengilegar hér [https://island.is/okuskirteini](https://island.is/okuskirteini)', + description: 'Digital driving license', + }, congratulationsTempHelpText: { id: 'dl.application:congratulationsTempHelpText', defaultMessage: From eac1952e331155ceb288eb637b51f584a8dc506c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Albert?= Date: Fri, 27 Sep 2024 16:39:45 +0000 Subject: [PATCH 10/18] chore(driving-license): quality photo check for 65+ (#16191) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/fields/QualityPhoto/QualityPhoto.tsx | 1 - .../src/forms/draft/subSectionQualityPhoto.ts | 2 +- .../driving-license/src/lib/utils/formUtils.ts | 13 ++++++------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/libs/application/templates/driving-license/src/fields/QualityPhoto/QualityPhoto.tsx b/libs/application/templates/driving-license/src/fields/QualityPhoto/QualityPhoto.tsx index 4190d4c2e3e8..4063f093a531 100644 --- a/libs/application/templates/driving-license/src/fields/QualityPhoto/QualityPhoto.tsx +++ b/libs/application/templates/driving-license/src/fields/QualityPhoto/QualityPhoto.tsx @@ -3,7 +3,6 @@ import React, { FC } from 'react' import { Box, Text, - ContentBlock, AlertMessage, SkeletonLoader, } from '@island.is/island-ui/core' diff --git a/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts b/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts index 0b598d74a8e9..351b04450050 100644 --- a/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts +++ b/libs/application/templates/driving-license/src/forms/draft/subSectionQualityPhoto.ts @@ -20,7 +20,7 @@ export const subSectionQualityPhoto = buildSubSection({ id: 'photoStep', title: m.applicationQualityPhotoTitle, condition: isVisible( - isApplicationForCondition(B_FULL || B_FULL_RENEWAL_65), + isApplicationForCondition([B_FULL, B_FULL_RENEWAL_65]), hasNoDrivingLicenseInOtherCountry, ), children: [ diff --git a/libs/application/templates/driving-license/src/lib/utils/formUtils.ts b/libs/application/templates/driving-license/src/lib/utils/formUtils.ts index 792e143e011c..69897b5d9bae 100644 --- a/libs/application/templates/driving-license/src/lib/utils/formUtils.ts +++ b/libs/application/templates/driving-license/src/lib/utils/formUtils.ts @@ -36,14 +36,13 @@ export const isVisible = } export const isApplicationForCondition = - (result: DrivingLicenseApplicationFor) => (answers: FormValue) => { - const applicationFor = - getValueViaPath( - answers, - 'applicationFor', - ) ?? B_FULL + (result: DrivingLicenseApplicationFor | DrivingLicenseApplicationFor[]) => + (answers: FormValue) => { + const strings = Array.isArray(result) ? result : [result] - return applicationFor === result + return strings.some( + (x) => x === getValueViaPath(answers, 'applicationFor') ?? B_FULL, + ) } export const hasNoDrivingLicenseInOtherCountry = (answers: FormValue) => From 775256aa475db0b2e6fa1c8c0cf8ebd4791e4077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gu=C3=B0j=C3=B3n=20Gu=C3=B0j=C3=B3nsson?= Date: Fri, 27 Sep 2024 18:49:17 +0200 Subject: [PATCH 11/18] fix(j-s): National Id Lookup Check (#16190) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../routes/Prosecutor/Indictments/Processing/Processing.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx index 63d9972e6ca3..c76de5e43862 100644 --- a/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx +++ b/apps/judicial-system/web/src/routes/Prosecutor/Indictments/Processing/Processing.tsx @@ -112,9 +112,7 @@ const Processing: FC = () => { civilClaimantNationalIdUpdate?.nationalId, ) - const stepIsValid = - isProcessingStepValidIndictments(workingCase) && - nationalIdNotFound === false + const stepIsValid = isProcessingStepValidIndictments(workingCase) const handleUpdateDefendant = useCallback( (updatedDefendant: UpdateDefendantInput) => { From a73abfa37faeee65dc563b26b251b596497accde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sigr=C3=BAn=20Tinna=20Gissurard=C3=B3ttir?= <39527334+sigruntg@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:57:34 +0000 Subject: [PATCH 12/18] feat(register-new-machine): add alert (#16188) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../MachineSection/MachineBasicInformation.ts | 10 ++++++++++ .../register-new-machine/src/lib/messages/machine.ts | 11 +++++++++++ .../aosh/register-new-machine/src/utils/index.ts | 1 + .../register-new-machine/src/utils/isNotCEmarked.ts | 12 ++++++++++++ 4 files changed, 34 insertions(+) create mode 100644 libs/application/templates/aosh/register-new-machine/src/utils/isNotCEmarked.ts diff --git a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts index 0bfe501432a7..22afa79349c9 100644 --- a/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts +++ b/libs/application/templates/aosh/register-new-machine/src/forms/RegisterMachineForm/MachineSection/MachineBasicInformation.ts @@ -1,6 +1,7 @@ import { NO, YES, + buildAlertMessageField, buildCustomField, buildDescriptionField, buildMultiField, @@ -11,6 +12,8 @@ import { import { information, machine } from '../../../lib/messages' import { NEW, USED } from '../../../shared/types' import { getAllCountryCodes } from '@island.is/shared/utils' +import { isNotCEmarked } from '../../../utils' +import { FormValue } from '@island.is/application/types' export const MachineBasicInformation = buildSubSection({ id: 'machineBasicInformation', @@ -131,6 +134,13 @@ export const MachineBasicInformation = buildSubSection({ width: 'half', maxLength: 50, }), + buildAlertMessageField({ + id: 'machine.basicInformation.alert', + title: machine.labels.basicMachineInformation.alertTitle, + message: machine.labels.basicMachineInformation.alertMessage, + alertType: 'warning', + condition: (answer: FormValue) => isNotCEmarked(answer), + }), ], }), ], diff --git a/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts b/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts index 1e9c5992949e..c9f442d38ac4 100644 --- a/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts +++ b/libs/application/templates/aosh/register-new-machine/src/lib/messages/machine.ts @@ -168,6 +168,17 @@ export const machine = { defaultMessage: 'Farmskrárnúmer', description: `Basic machine information cargo file number label`, }, + alertTitle: { + id: 'aosh.rnm.application:machine.labels.basicMachineInformation.alertTitle', + defaultMessage: 'CE merking', + description: `Basic machine information alert title`, + }, + alertMessage: { + id: 'aosh.rnm.application:machine.labels.basicMachineInformation.alertMessage', + defaultMessage: + 'Ef tæki er ekki CE merkt þarf að skila inn samræmisyfirlýsingu og fleiri viðbótargögnum.', + description: `Basic machine information alert message`, + }, }), technicalMachineInformation: defineMessages({ sectionTitle: { diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/index.ts b/libs/application/templates/aosh/register-new-machine/src/utils/index.ts index cf31e24a55c7..fdfad9f918e5 100644 --- a/libs/application/templates/aosh/register-new-machine/src/utils/index.ts +++ b/libs/application/templates/aosh/register-new-machine/src/utils/index.ts @@ -10,3 +10,4 @@ export * from './formatPhoneNumber' export * from './formatDate' export * from './doOwnerAndImporterHaveSameNationalId' export * from './doOwnerAndOperatorHaveSameNationalId' +export * from './isNotCEmarked' diff --git a/libs/application/templates/aosh/register-new-machine/src/utils/isNotCEmarked.ts b/libs/application/templates/aosh/register-new-machine/src/utils/isNotCEmarked.ts new file mode 100644 index 000000000000..0d18b63c878a --- /dev/null +++ b/libs/application/templates/aosh/register-new-machine/src/utils/isNotCEmarked.ts @@ -0,0 +1,12 @@ +import { getValueViaPath } from '@island.is/application/core' +import { FormValue, NO, YES } from '@island.is/application/types' + +export const isNotCEmarked = (answers: FormValue) => { + const markedCE = getValueViaPath( + answers, + 'machine.basicInformation.markedCE', + YES, + ) as typeof NO | typeof YES + + return markedCE === NO +} From 211c5b5fb59731bfbaefd17af00f8d7cfed2547e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:15:09 +0000 Subject: [PATCH 13/18] fix(web): Explicitly pass down elastic index (#16199) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/content-search-indexer/src/lib/indexing.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/libs/content-search-indexer/src/lib/indexing.service.ts b/libs/content-search-indexer/src/lib/indexing.service.ts index ee38d07c8ea6..16be829458a5 100644 --- a/libs/content-search-indexer/src/lib/indexing.service.ts +++ b/libs/content-search-indexer/src/lib/indexing.service.ts @@ -72,6 +72,7 @@ export class IndexingService { while (initialFetch || nextPageToken) { const importerResponse = await importer.doSync({ ...options, + elasticIndex, nextPageToken, folderHash: postSyncOptions?.folderHash, }) From cb65af364c359f90d83551a6da4d24cd40733175 Mon Sep 17 00:00:00 2001 From: valurefugl <65780958+valurefugl@users.noreply.github.com> Date: Fri, 27 Sep 2024 18:21:08 +0100 Subject: [PATCH 14/18] fix(auth-api): Fix url for syslumenn api (#16193) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/services/auth/admin-api/infra/auth-admin-api.ts | 2 +- .../auth/delegation-api/infra/delegation-api.ts | 2 +- apps/services/auth/ids-api/infra/ids-api.ts | 2 +- .../infra/personal-representative.ts | 2 +- apps/services/auth/public-api/infra/auth-public-api.ts | 2 +- charts/identity-server/values.prod.yaml | 10 +++++----- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/apps/services/auth/admin-api/infra/auth-admin-api.ts b/apps/services/auth/admin-api/infra/auth-admin-api.ts index fff13b8131cc..376206480242 100644 --- a/apps/services/auth/admin-api/infra/auth-admin-api.ts +++ b/apps/services/auth/admin-api/infra/auth-admin-api.ts @@ -62,7 +62,7 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-admin-api'> => { SYSLUMENN_HOST: { dev: 'https://api.syslumenn.is/staging', staging: 'https://api.syslumenn.is/staging', - prod: 'https://api.syslumenn.is', + prod: 'https://api.syslumenn.is/api', }, SYSLUMENN_TIMEOUT: '3000', ZENDESK_CONTACT_FORM_SUBDOMAIN: { diff --git a/apps/services/auth/delegation-api/infra/delegation-api.ts b/apps/services/auth/delegation-api/infra/delegation-api.ts index 60202a6822d8..eea421e6e077 100644 --- a/apps/services/auth/delegation-api/infra/delegation-api.ts +++ b/apps/services/auth/delegation-api/infra/delegation-api.ts @@ -57,7 +57,7 @@ export const serviceSetup = (services: { SYSLUMENN_HOST: { dev: 'https://api.syslumenn.is/staging', staging: 'https://api.syslumenn.is/staging', - prod: 'https://api.syslumenn.is', + prod: 'https://api.syslumenn.is/api', }, SYSLUMENN_TIMEOUT: '3000', }) diff --git a/apps/services/auth/ids-api/infra/ids-api.ts b/apps/services/auth/ids-api/infra/ids-api.ts index e72a270d0d37..14ac4f6c60bf 100644 --- a/apps/services/auth/ids-api/infra/ids-api.ts +++ b/apps/services/auth/ids-api/infra/ids-api.ts @@ -86,7 +86,7 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-ids-api'> => { SYSLUMENN_HOST: { dev: 'https://api.syslumenn.is/staging', staging: 'https://api.syslumenn.is/staging', - prod: 'https://api.syslumenn.is', + prod: 'https://api.syslumenn.is/api', }, SYSLUMENN_TIMEOUT: '3000', }) diff --git a/apps/services/auth/personal-representative/infra/personal-representative.ts b/apps/services/auth/personal-representative/infra/personal-representative.ts index bb3c79004506..6f10744c5a99 100644 --- a/apps/services/auth/personal-representative/infra/personal-representative.ts +++ b/apps/services/auth/personal-representative/infra/personal-representative.ts @@ -45,7 +45,7 @@ export const serviceSetup = SYSLUMENN_HOST: { dev: 'https://api.syslumenn.is/staging', staging: 'https://api.syslumenn.is/staging', - prod: 'https://api.syslumenn.is', + prod: 'https://api.syslumenn.is/api', }, SYSLUMENN_TIMEOUT: '3000', }) diff --git a/apps/services/auth/public-api/infra/auth-public-api.ts b/apps/services/auth/public-api/infra/auth-public-api.ts index 28e81a88467a..3108257b6d75 100644 --- a/apps/services/auth/public-api/infra/auth-public-api.ts +++ b/apps/services/auth/public-api/infra/auth-public-api.ts @@ -66,7 +66,7 @@ export const serviceSetup = (): ServiceBuilder<'services-auth-public-api'> => { SYSLUMENN_HOST: { dev: 'https://api.syslumenn.is/staging', staging: 'https://api.syslumenn.is/staging', - prod: 'https://api.syslumenn.is', + prod: 'https://api.syslumenn.is/api', }, SYSLUMENN_TIMEOUT: '3000', }) diff --git a/charts/identity-server/values.prod.yaml b/charts/identity-server/values.prod.yaml index 1c3c4d8a443b..077392613786 100644 --- a/charts/identity-server/values.prod.yaml +++ b/charts/identity-server/values.prod.yaml @@ -222,7 +222,7 @@ services-auth-admin-api: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=691 -r dd-trace/init' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SYSLUMENN_HOST: 'https://api.syslumenn.is' + SYSLUMENN_HOST: 'https://api.syslumenn.is/api' SYSLUMENN_TIMEOUT: '3000' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' @@ -312,7 +312,7 @@ services-auth-delegation-api: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SYSLUMENN_HOST: 'https://api.syslumenn.is' + SYSLUMENN_HOST: 'https://api.syslumenn.is/api' SYSLUMENN_TIMEOUT: '3000' USER_NOTIFICATION_API_URL: 'https://user-notification.internal.island.is' XROAD_BASE_PATH: 'http://securityserver.island.is' @@ -412,7 +412,7 @@ services-auth-ids-api: PUBLIC_URL: 'https://innskra.island.is/api' REDIS_NODES: '["clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SYSLUMENN_HOST: 'https://api.syslumenn.is' + SYSLUMENN_HOST: 'https://api.syslumenn.is/api' SYSLUMENN_TIMEOUT: '3000' USER_PROFILE_CLIENT_SCOPE: '["@island.is/user-profile:read"]' USER_PROFILE_CLIENT_URL: 'https://service-portal-api.internal.island.is' @@ -586,7 +586,7 @@ services-auth-personal-representative: LOG_LEVEL: 'info' NODE_OPTIONS: '--max-old-space-size=460 -r dd-trace/init' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SYSLUMENN_HOST: 'https://api.syslumenn.is' + SYSLUMENN_HOST: 'https://api.syslumenn.is/api' SYSLUMENN_TIMEOUT: '3000' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' @@ -743,7 +743,7 @@ services-auth-public-api: PUBLIC_URL: 'https://innskra.island.is/api' REDIS_NODES: '["clustercfg.general-redis-cluster-group.dnugi2.euw1.cache.amazonaws.com:6379"]' SERVERSIDE_FEATURES_ON: 'driving-license-use-v1-endpoint-for-v2-comms' - SYSLUMENN_HOST: 'https://api.syslumenn.is' + SYSLUMENN_HOST: 'https://api.syslumenn.is/api' SYSLUMENN_TIMEOUT: '3000' XROAD_BASE_PATH: 'http://securityserver.island.is' XROAD_BASE_PATH_WITH_ENV: 'http://securityserver.island.is/r1/IS' From 85443f81c14c64c5f910fc6cf30914aa24632fff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAnar=20Vestmann?= <43557895+RunarVestmann@users.noreply.github.com> Date: Sun, 29 Sep 2024 11:57:32 +0000 Subject: [PATCH 15/18] feat(web): FAQ List and Accordion - Set single expand to false (#15984) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../Organization/Slice/AccordionSlice/AccordionSlice.tsx | 2 +- libs/island-ui/contentful/src/lib/FaqList/FaqList.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/components/Organization/Slice/AccordionSlice/AccordionSlice.tsx b/apps/web/components/Organization/Slice/AccordionSlice/AccordionSlice.tsx index 5d24e2df9b57..603778145701 100644 --- a/apps/web/components/Organization/Slice/AccordionSlice/AccordionSlice.tsx +++ b/apps/web/components/Organization/Slice/AccordionSlice/AccordionSlice.tsx @@ -60,7 +60,7 @@ export const AccordionSlice: React.FC> = ({ ))} {slice.type === 'accordion_minimal' && ( - + {(slice.accordionItems ?? []).map((item) => ( > = ({ {title} )} - + {questions.map(({ id, question, answer }) => { return ( Date: Sun, 29 Sep 2024 12:39:52 +0000 Subject: [PATCH 16/18] fix(web): Change useWindowSize function to react-use for more stability on load (#16201) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- apps/web/hooks/useViewport.ts | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/apps/web/hooks/useViewport.ts b/apps/web/hooks/useViewport.ts index d2ee3e3c1505..4815617572c8 100644 --- a/apps/web/hooks/useViewport.ts +++ b/apps/web/hooks/useViewport.ts @@ -1,4 +1,5 @@ -import { useState, useEffect, useCallback } from 'react' +import { useCallback, useEffect, useState } from 'react' +import { useWindowSize } from 'react-use' type Position = { x: number @@ -28,33 +29,6 @@ export const useScrollPosition = (): Position => { return position } -export const useWindowSize = (): Rect => { - const [size, setSize] = useState({ width: 0, height: 0 }) - - const onResize = useCallback(() => { - setSize({ - width: - window.innerWidth || - document.documentElement.clientWidth || - document.body.clientWidth, - height: - window.innerHeight || - document.documentElement.clientHeight || - document.body.clientHeight, - }) - }, []) - - useEffect(() => { - onResize() - window.addEventListener('resize', onResize, { passive: true }) - return () => { - window.removeEventListener('resize', onResize) - } - }, [onResize]) - - return size -} - const useViewport = (): [Position, Rect] => { const position = useScrollPosition() const size = useWindowSize() @@ -63,3 +37,5 @@ const useViewport = (): [Position, Rect] => { } export default useViewport + +export { useWindowSize } From c14f8689e620459ae9c5cf33011bf4b9124145e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9Eorkell=20M=C3=A1ni=20=C3=9Eorkelsson?= Date: Sun, 29 Sep 2024 13:25:24 +0000 Subject: [PATCH 17/18] feat(assets): Bulk mileage registration (#15774) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Vehicle bulk mileage frontend * feat: omg it works * chore: remove logs * chore: nx format:write update dirty files * fix: more messsages * feat: bad csv parser * fix: failure calblack * feat: refactor logic * chore: nx format:write update dirty files * feat: better org * chore: update config * feat: update client * feat: update with mutation * feat: organize domain and add methods * feat: update domain * fix: better csv parsing * chore: label * chore: nx format:write update dirty files * feat: some ui * chore: nx format:write update dirty files * feat/clearer ui * chore: empty screen * fix: remove buttons * fix: expand callbacks * fix: linting * fix: expand lower * chore: remove console * chore: localize messages * chore: imports * fix: add logos * fix: parsing * fix: reveiw comments * chore: nx format:write update dirty files * fix: more review fixes * chore: review comment v3 * chore: fix func name * chore: review2 gp * chore: add error message * fix:nullechck * fix:review --------- Co-authored-by: Þórður Hafliðason Co-authored-by: andes-it Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- libs/api/domains/vehicles/src/index.ts | 3 +- .../src/lib/api-domains-vehicles.type.ts | 183 ------ .../api/domains/vehicles/src/lib/constants.ts | 2 + ...BulkVehicleMileageRequestOverview.input.ts | 7 + ...etBulkVehicleMileageRequestStatus.input.ts | 7 + .../dto/getPublicVehicleSearchInput.ts | 0 .../{ => lib}/dto/getVehicleDetailInput.ts | 0 .../{ => lib}/dto/getVehicleMileageInput.ts | 0 .../{ => lib}/dto/getVehicleSearchInput.ts | 0 .../{ => lib}/dto/getVehiclesForUserInput.ts | 0 .../src/lib/dto/mileageReading.dto.ts | 9 + .../lib/dto/postBulkVehicleMileage.input.ts | 22 + .../{ => lib}/dto/postVehicleMileageInput.ts | 0 .../src/lib/dto/vehiclesListInputV3.ts | 10 + .../models/getPublicVehicleSearch.model.ts | 0 .../models/getVehicleDetail.model.ts | 0 .../models/getVehicleMileage.model.ts | 0 .../models/getVehicleSearch.model.ts | 0 .../{ => lib}/models/usersVehicles.model.ts | 8 + .../bulkMileageReadingResponse.model.ts | 13 + .../bulkMileageRegistrationJob.model.ts | 37 ++ ...bulkMileageRegistrationJobHistory.model.ts | 8 + ...kMileageRegistrationRequestDetail.model.ts | 22 + ...lkMileageRegistrationRequestError.model.ts | 10 + ...ileageRegistrationRequestOverview.model.ts | 8 + ...kMileageRegistrationRequestStatus.model.ts | 22 + .../v3/currentVehicleListResponse.model.ts | 20 + .../v3/currentVehicleWithMileage.model.ts | 23 + .../src/lib/models/v3/mileageDetails.model.ts | 14 + .../models/v3/mileageRegistration.model.ts | 13 + .../v3/mileageRegistrationHistory.model.ts | 14 + .../src/lib/resolvers/bulkMileage.resolver.ts | 84 +++ .../mileage.resolver.ts} | 6 +- .../shared.resolver.ts} | 2 +- .../src/lib/resolvers/vehicleV3.resolver.ts | 48 ++ .../vehicles.resolver.ts} | 2 +- .../src/lib/services/bulkMileage.service.ts | 143 +++++ .../vehicles.service.ts} | 121 +++- .../utils/basicVehicleInformationMapper.ts | 0 .../vehicles/src/{ => lib}/utils/helpers.ts | 0 ...-vehicles.module.ts => vehicles.module.ts} | 14 +- libs/clients/vehicles-mileage/project.json | 3 +- .../vehicles-mileage/src/clientConfig.json | 563 +++++++++++++++++- libs/feature-flags/src/lib/features.ts | 1 + libs/island-ui/core/src/lib/Icon/Icon.tsx | 7 + libs/island-ui/core/src/lib/IconRC/iconMap.ts | 3 + .../core/src/lib/IconRC/icons/Upload.tsx | 23 + .../src/lib/IconRC/icons/UploadOutline.tsx | 38 ++ .../service-portal/assets/src/lib/messages.ts | 169 ++++++ .../assets/src/lib/navigation.ts | 23 + libs/service-portal/assets/src/lib/paths.ts | 4 + libs/service-portal/assets/src/module.tsx | 45 ++ .../patentVariations/IS.tsx | 1 - .../VehicleBulkMileage.css.ts | 11 + .../VehicleBulkMileage.graphql | 37 ++ .../VehicleBulkMileage/VehicleBulkMileage.tsx | 146 +++++ .../VehicleBulkMileageFileDownloader.tsx | 46 ++ .../VehicleBulkMileageRow.tsx | 160 +++++ .../VehicleBulkMileageSaveButton.tsx | 56 ++ .../VehicleBulkMileageTable.tsx | 88 +++ .../VehicleBulkMileage/mocks/propsDummy.ts | 284 +++++++++ .../src/screens/VehicleBulkMileage/types.ts | 34 ++ .../VehicleBulkMileageJobDetail.graphql | 24 + .../VehicleBulkMileageJobDetail.tsx | 242 ++++++++ .../VehicleBulkMileageJobOverview.graphql | 14 + .../VehicleBulkMileageJobOverview.tsx | 148 +++++ .../VehicleBulkMileageUpload.graphql | 6 + .../VehicleBulkMileageUpload.tsx | 174 ++++++ .../assets/src/utils/makeArrayEven.ts | 2 +- .../assets/src/utils/parseCsvToMileage.ts | 58 ++ .../assets/src/utils/vehicleOwnedMapper.ts | 10 +- .../src/components/LinkButton/LinkButton.tsx | 16 +- .../NestedTable/NestedFullTable.tsx | 76 +++ .../components/NestedTable/NestedTable.css.ts | 18 + libs/service-portal/core/src/index.ts | 1 + libs/service-portal/core/src/lib/messages.ts | 33 + 76 files changed, 3209 insertions(+), 230 deletions(-) delete mode 100644 libs/api/domains/vehicles/src/lib/api-domains-vehicles.type.ts create mode 100644 libs/api/domains/vehicles/src/lib/constants.ts create mode 100644 libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts create mode 100644 libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestStatus.input.ts rename libs/api/domains/vehicles/src/{ => lib}/dto/getPublicVehicleSearchInput.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/dto/getVehicleDetailInput.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/dto/getVehicleMileageInput.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/dto/getVehicleSearchInput.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/dto/getVehiclesForUserInput.ts (100%) create mode 100644 libs/api/domains/vehicles/src/lib/dto/mileageReading.dto.ts create mode 100644 libs/api/domains/vehicles/src/lib/dto/postBulkVehicleMileage.input.ts rename libs/api/domains/vehicles/src/{ => lib}/dto/postVehicleMileageInput.ts (100%) create mode 100644 libs/api/domains/vehicles/src/lib/dto/vehiclesListInputV3.ts rename libs/api/domains/vehicles/src/{ => lib}/models/getPublicVehicleSearch.model.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/models/getVehicleDetail.model.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/models/getVehicleMileage.model.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/models/getVehicleSearch.model.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/models/usersVehicles.model.ts (94%) create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageReadingResponse.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJob.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestDetail.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/currentVehicleListResponse.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/currentVehicleWithMileage.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/mileageDetails.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/mileageRegistration.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/models/v3/mileageRegistrationHistory.model.ts create mode 100644 libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts rename libs/api/domains/vehicles/src/lib/{api-domains-vehicles-mileage.resolver.ts => resolvers/mileage.resolver.ts} (95%) rename libs/api/domains/vehicles/src/lib/{api-domains-vehicles-shared.resolver.ts => resolvers/shared.resolver.ts} (95%) create mode 100644 libs/api/domains/vehicles/src/lib/resolvers/vehicleV3.resolver.ts rename libs/api/domains/vehicles/src/lib/{api-domains-vehicles.resolver.ts => resolvers/vehicles.resolver.ts} (98%) create mode 100644 libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts rename libs/api/domains/vehicles/src/lib/{api-domains-vehicles.service.ts => services/vehicles.service.ts} (75%) rename libs/api/domains/vehicles/src/{ => lib}/utils/basicVehicleInformationMapper.ts (100%) rename libs/api/domains/vehicles/src/{ => lib}/utils/helpers.ts (100%) rename libs/api/domains/vehicles/src/lib/{api-domains-vehicles.module.ts => vehicles.module.ts} (52%) create mode 100644 libs/island-ui/core/src/lib/IconRC/icons/Upload.tsx create mode 100644 libs/island-ui/core/src/lib/IconRC/icons/UploadOutline.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.css.ts create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.graphql create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageFileDownloader.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSaveButton.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageTable.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/mocks/propsDummy.ts create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileage/types.ts create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.graphql create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.graphql create mode 100644 libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx create mode 100644 libs/service-portal/assets/src/utils/parseCsvToMileage.ts create mode 100644 libs/service-portal/core/src/components/NestedTable/NestedFullTable.tsx diff --git a/libs/api/domains/vehicles/src/index.ts b/libs/api/domains/vehicles/src/index.ts index 3bf603730d35..3b36038fbea0 100644 --- a/libs/api/domains/vehicles/src/index.ts +++ b/libs/api/domains/vehicles/src/index.ts @@ -1,2 +1 @@ -export * from './lib/api-domains-vehicles.module' -export * from './lib/api-domains-vehicles.service' +export * from './lib/vehicles.module' diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.type.ts b/libs/api/domains/vehicles/src/lib/api-domains-vehicles.type.ts deleted file mode 100644 index 402acbf3d765..000000000000 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.type.ts +++ /dev/null @@ -1,183 +0,0 @@ -/** - * - * @export - * @interface Vehicle - */ -export interface Vehicle { - /** - * - * @type {boolean} - * @memberof Vehicle - */ - isCurrent?: boolean - /** - * - * @type {string} - * @memberof Vehicle - */ - permno?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - regno?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - vin?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - type?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - color?: string | null - /** - * - * @type {Date} - * @memberof Vehicle - */ - firstRegDate?: Date | null - /** - * - * @type {string} - * @memberof Vehicle - */ - modelYear?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - productYear?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - registrationType?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - role?: string | null - /** - * - * @type {Date} - * @memberof Vehicle - */ - operatorStartDate?: Date | null - /** - * - * @type {Date} - * @memberof Vehicle - */ - operatorEndDate?: Date | null - /** - * - * @type {boolean} - * @memberof Vehicle - */ - outOfUse?: boolean - /** - * - * @type {boolean} - * @memberof Vehicle - */ - otherOwners?: boolean - /** - * - * @type {string} - * @memberof Vehicle - */ - termination?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - buyerPersidno?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - ownerPersidno?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - vehicleStatus?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - useGroup?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - vehGroup?: string | null - /** - * - * @type {string} - * @memberof Vehicle - */ - plateStatus?: string | null -} - -/** - * - * @export - * @interface PersidnoLookup - */ -export interface UsersVehicles { - /** - * - * @type {string} - * @memberof PersidnoLookup - */ - persidno?: string | null - /** - * - * @type {string} - * @memberof PersidnoLookup - */ - name?: string | null - /** - * - * @type {string} - * @memberof PersidnoLookup - */ - address?: string | null - /** - * - * @type {string} - * @memberof PersidnoLookup - */ - postStation?: string | null - /** - * - * @type {Array} - * @memberof PersidnoLookup - */ - vehicleList?: Array | null - /** - * - * @type {string} - * @memberof PersidnoLookup - */ - createdTimestamp?: string | null -} diff --git a/libs/api/domains/vehicles/src/lib/constants.ts b/libs/api/domains/vehicles/src/lib/constants.ts new file mode 100644 index 000000000000..7d200c985f78 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/constants.ts @@ -0,0 +1,2 @@ +export const ISLAND_IS_ORIGIN_CODE = 'ISLAND.IS' +export const LOG_CATEGORY = 'api-domains-vehicles' diff --git a/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts new file mode 100644 index 000000000000..65b238fb9150 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestOverview.input.ts @@ -0,0 +1,7 @@ +import { Field, ID, InputType } from '@nestjs/graphql' + +@InputType() +export class BulkVehicleMileageRequestOverviewInput { + @Field(() => ID) + guid!: string +} diff --git a/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestStatus.input.ts b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestStatus.input.ts new file mode 100644 index 000000000000..077fb1f9fdcf --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/dto/getBulkVehicleMileageRequestStatus.input.ts @@ -0,0 +1,7 @@ +import { Field, ID, InputType } from '@nestjs/graphql' + +@InputType() +export class BulkVehicleMileageRequestStatusInput { + @Field(() => ID) + requestId!: string +} diff --git a/libs/api/domains/vehicles/src/dto/getPublicVehicleSearchInput.ts b/libs/api/domains/vehicles/src/lib/dto/getPublicVehicleSearchInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/getPublicVehicleSearchInput.ts rename to libs/api/domains/vehicles/src/lib/dto/getPublicVehicleSearchInput.ts diff --git a/libs/api/domains/vehicles/src/dto/getVehicleDetailInput.ts b/libs/api/domains/vehicles/src/lib/dto/getVehicleDetailInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/getVehicleDetailInput.ts rename to libs/api/domains/vehicles/src/lib/dto/getVehicleDetailInput.ts diff --git a/libs/api/domains/vehicles/src/dto/getVehicleMileageInput.ts b/libs/api/domains/vehicles/src/lib/dto/getVehicleMileageInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/getVehicleMileageInput.ts rename to libs/api/domains/vehicles/src/lib/dto/getVehicleMileageInput.ts diff --git a/libs/api/domains/vehicles/src/dto/getVehicleSearchInput.ts b/libs/api/domains/vehicles/src/lib/dto/getVehicleSearchInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/getVehicleSearchInput.ts rename to libs/api/domains/vehicles/src/lib/dto/getVehicleSearchInput.ts diff --git a/libs/api/domains/vehicles/src/dto/getVehiclesForUserInput.ts b/libs/api/domains/vehicles/src/lib/dto/getVehiclesForUserInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/getVehiclesForUserInput.ts rename to libs/api/domains/vehicles/src/lib/dto/getVehiclesForUserInput.ts diff --git a/libs/api/domains/vehicles/src/lib/dto/mileageReading.dto.ts b/libs/api/domains/vehicles/src/lib/dto/mileageReading.dto.ts new file mode 100644 index 000000000000..7fbef841c313 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/dto/mileageReading.dto.ts @@ -0,0 +1,9 @@ +export interface MileageReadingDto { + isEditing: boolean + canUserRegisterVehicleMileage?: boolean + readings: Array<{ + date?: Date + origin?: string + mileage?: number + }> +} diff --git a/libs/api/domains/vehicles/src/lib/dto/postBulkVehicleMileage.input.ts b/libs/api/domains/vehicles/src/lib/dto/postBulkVehicleMileage.input.ts new file mode 100644 index 000000000000..40047f8c456e --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/dto/postBulkVehicleMileage.input.ts @@ -0,0 +1,22 @@ +import { Field, InputType } from '@nestjs/graphql' +import { IsInt, IsString } from 'class-validator' + +@InputType() +export class PostVehicleBulkMileageInput { + @Field({ description: 'Example: "ISLAND.IS"' }) + originCode!: string + + @Field(() => [PostVehicleBulkMileageSingleInput]) + mileageData!: Array +} + +@InputType() +export class PostVehicleBulkMileageSingleInput { + @Field() + @IsString() + vehicleId!: string + + @Field() + @IsInt() + mileageNumber!: number +} diff --git a/libs/api/domains/vehicles/src/dto/postVehicleMileageInput.ts b/libs/api/domains/vehicles/src/lib/dto/postVehicleMileageInput.ts similarity index 100% rename from libs/api/domains/vehicles/src/dto/postVehicleMileageInput.ts rename to libs/api/domains/vehicles/src/lib/dto/postVehicleMileageInput.ts diff --git a/libs/api/domains/vehicles/src/lib/dto/vehiclesListInputV3.ts b/libs/api/domains/vehicles/src/lib/dto/vehiclesListInputV3.ts new file mode 100644 index 000000000000..15c75e1ccd8e --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/dto/vehiclesListInputV3.ts @@ -0,0 +1,10 @@ +import { Field, InputType } from '@nestjs/graphql' + +@InputType() +export class VehiclesListInputV3 { + @Field() + pageSize!: number + + @Field() + page!: number +} diff --git a/libs/api/domains/vehicles/src/models/getPublicVehicleSearch.model.ts b/libs/api/domains/vehicles/src/lib/models/getPublicVehicleSearch.model.ts similarity index 100% rename from libs/api/domains/vehicles/src/models/getPublicVehicleSearch.model.ts rename to libs/api/domains/vehicles/src/lib/models/getPublicVehicleSearch.model.ts diff --git a/libs/api/domains/vehicles/src/models/getVehicleDetail.model.ts b/libs/api/domains/vehicles/src/lib/models/getVehicleDetail.model.ts similarity index 100% rename from libs/api/domains/vehicles/src/models/getVehicleDetail.model.ts rename to libs/api/domains/vehicles/src/lib/models/getVehicleDetail.model.ts diff --git a/libs/api/domains/vehicles/src/models/getVehicleMileage.model.ts b/libs/api/domains/vehicles/src/lib/models/getVehicleMileage.model.ts similarity index 100% rename from libs/api/domains/vehicles/src/models/getVehicleMileage.model.ts rename to libs/api/domains/vehicles/src/lib/models/getVehicleMileage.model.ts diff --git a/libs/api/domains/vehicles/src/models/getVehicleSearch.model.ts b/libs/api/domains/vehicles/src/lib/models/getVehicleSearch.model.ts similarity index 100% rename from libs/api/domains/vehicles/src/models/getVehicleSearch.model.ts rename to libs/api/domains/vehicles/src/lib/models/getVehicleSearch.model.ts diff --git a/libs/api/domains/vehicles/src/models/usersVehicles.model.ts b/libs/api/domains/vehicles/src/lib/models/usersVehicles.model.ts similarity index 94% rename from libs/api/domains/vehicles/src/models/usersVehicles.model.ts rename to libs/api/domains/vehicles/src/lib/models/usersVehicles.model.ts index 7ef014591272..04476ff794ae 100644 --- a/libs/api/domains/vehicles/src/models/usersVehicles.model.ts +++ b/libs/api/domains/vehicles/src/lib/models/usersVehicles.model.ts @@ -1,4 +1,5 @@ import { Field, ObjectType } from '@nestjs/graphql' +import { VehicleMileageDetail } from './getVehicleMileage.model' @ObjectType() export class NextInspection { @@ -11,6 +12,7 @@ export class NextInspection { }) nextinspectiondateIfPassedInspectionToday?: Date } + @ObjectType() export class VehiclesVehicle { @Field({ nullable: true }) @@ -243,6 +245,12 @@ export class VehicleListed { @Field({ nullable: true }) nextMainInspection?: Date + + @Field(() => VehicleMileageDetail, { nullable: true }) + lastMileageRegistration?: VehicleMileageDetail + + @Field(() => [VehicleMileageDetail], { nullable: true }) + mileageRegistrationHistory?: Array } @ObjectType() diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageReadingResponse.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageReadingResponse.model.ts new file mode 100644 index 000000000000..ef8738dc461f --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageReadingResponse.model.ts @@ -0,0 +1,13 @@ +import { Field, ObjectType, ID } from '@nestjs/graphql' + +@ObjectType() +export class VehiclesBulkMileageReadingResponse { + @Field(() => ID, { + description: + 'The GUID of the mileage registration post request. Used to fetch job status', + }) + requestId!: string + + @Field({ nullable: true }) + errorMessage?: string +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJob.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJob.model.ts new file mode 100644 index 000000000000..a677030b08d6 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJob.model.ts @@ -0,0 +1,37 @@ +import { Field, ObjectType, ID, GraphQLISODateTime } from '@nestjs/graphql' + +@ObjectType() +export class VehiclesBulkMileageRegistrationJob { + @Field(() => ID) + guid!: string + + @Field({ nullable: true }) + reportingPersonNationalId?: string + + @Field({ nullable: true }) + reportingPersonName?: string + + @Field({ nullable: true }) + originCode?: string + + @Field({ nullable: true }) + originName?: string + + @Field(() => GraphQLISODateTime, { + nullable: true, + description: 'When was the bulk request requested?', + }) + dateRequested?: Date + + @Field(() => GraphQLISODateTime, { + nullable: true, + description: 'When did the bulk request start executing?', + }) + dateStarted?: Date + + @Field(() => GraphQLISODateTime, { + nullable: true, + description: 'When did the bulk request execution finish', + }) + dateFinished?: Date +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model.ts new file mode 100644 index 000000000000..f0ea92c3cc34 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model.ts @@ -0,0 +1,8 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { VehiclesBulkMileageRegistrationJob } from './bulkMileageRegistrationJob.model' + +@ObjectType() +export class VehiclesBulkMileageRegistrationJobHistory { + @Field(() => [VehiclesBulkMileageRegistrationJob]) + history!: Array +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestDetail.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestDetail.model.ts new file mode 100644 index 000000000000..804f11bcb951 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestDetail.model.ts @@ -0,0 +1,22 @@ +import { Field, ObjectType, ID, Int } from '@nestjs/graphql' +import { VehiclesBulkMileageRegistrationRequestError } from './bulkMileageRegistrationRequestError.model' + +@ObjectType() +export class VehiclesBulkMileageRegistrationRequestDetail { + @Field(() => ID) + guid!: string + + @Field() + vehicleId!: string + + @Field(() => Int, { nullable: true }) + mileage?: number + + @Field({ nullable: true }) + returnCode?: string + + @Field(() => [VehiclesBulkMileageRegistrationRequestError], { + nullable: true, + }) + errors?: Array +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts new file mode 100644 index 000000000000..a289df9b6cfa --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestError.model.ts @@ -0,0 +1,10 @@ +import { Field, ObjectType } from '@nestjs/graphql' + +@ObjectType() +export class VehiclesBulkMileageRegistrationRequestError { + @Field({ nullable: true }) + code?: string + + @Field({ nullable: true }) + message?: string +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model.ts new file mode 100644 index 000000000000..56b6f18ed3d5 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model.ts @@ -0,0 +1,8 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { VehiclesBulkMileageRegistrationRequestDetail } from './bulkMileageRegistrationRequestDetail.model' + +@ObjectType() +export class VehiclesBulkMileageRegistrationRequestOverview { + @Field(() => [VehiclesBulkMileageRegistrationRequestDetail]) + requests!: Array +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model.ts new file mode 100644 index 000000000000..587fdf36f1ee --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model.ts @@ -0,0 +1,22 @@ +import { Field, ID, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType() +export class VehiclesBulkMileageRegistrationRequestStatus { + @Field(() => ID) + requestId!: string + + @Field(() => Int, { nullable: true }) + jobsSubmitted?: number + + @Field(() => Int, { nullable: true }) + jobsFinished?: number + + @Field(() => Int, { nullable: true }) + jobsRemaining?: number + + @Field(() => Int, { nullable: true }) + jobsValid?: number + + @Field(() => Int, { nullable: true }) + jobsErrored?: number +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleListResponse.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleListResponse.model.ts new file mode 100644 index 000000000000..e1932c9b7c0d --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleListResponse.model.ts @@ -0,0 +1,20 @@ +import { Field, Int, ObjectType } from '@nestjs/graphql' +import { VehicleCurrentWithMileage } from './currentVehicleWithMileage.model' + +@ObjectType() +export class VehiclesCurrentListResponse { + @Field(() => Int) + pageNumber!: number + + @Field(() => Int) + pageSize!: number + + @Field(() => Int) + totalPages!: number + + @Field(() => Int) + totalRecords!: number + + @Field(() => [VehicleCurrentWithMileage], { nullable: true }) + data?: Array +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleWithMileage.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleWithMileage.model.ts new file mode 100644 index 000000000000..3884dd8d5be5 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/currentVehicleWithMileage.model.ts @@ -0,0 +1,23 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { MileageDetails } from './mileageDetails.model' + +@ObjectType() +export class VehicleCurrentWithMileage { + @Field() + vehicleId!: string + + @Field({ nullable: true }) + registrationNumber?: string + + @Field({ nullable: true }) + userRole?: string + + @Field({ nullable: true }) + type?: string + + @Field({ nullable: true }) + color?: string + + @Field(() => MileageDetails, { nullable: true }) + mileageDetails?: MileageDetails +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/mileageDetails.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/mileageDetails.model.ts new file mode 100644 index 000000000000..e2dd8408f696 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/mileageDetails.model.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { MileageRegistrationHistory } from './mileageRegistrationHistory.model' + +@ObjectType('VehiclesMileageDetails') +export class MileageDetails { + @Field() + canRegisterMileage!: boolean + + @Field() + requiresMileageRegistration!: boolean + + @Field(() => MileageRegistrationHistory, { nullable: true }) + mileageRegistrations?: MileageRegistrationHistory +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistration.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistration.model.ts new file mode 100644 index 000000000000..1585acb1d381 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistration.model.ts @@ -0,0 +1,13 @@ +import { Field, GraphQLISODateTime, Int, ObjectType } from '@nestjs/graphql' + +@ObjectType('VehiclesMileageRegistration') +export class MileageRegistration { + @Field() + originCode!: string + + @Field(() => Int) + mileage!: number + + @Field(() => GraphQLISODateTime) + date!: Date +} diff --git a/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistrationHistory.model.ts b/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistrationHistory.model.ts new file mode 100644 index 000000000000..dde6de0404c5 --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/models/v3/mileageRegistrationHistory.model.ts @@ -0,0 +1,14 @@ +import { Field, ObjectType } from '@nestjs/graphql' +import { MileageRegistration } from './mileageRegistration.model' + +@ObjectType('VehiclesMileageRegistrationHistory') +export class MileageRegistrationHistory { + @Field() + vehicleId!: string + + @Field(() => MileageRegistration, { nullable: true }) + lastMileageRegistration?: MileageRegistration + + @Field(() => [MileageRegistration], { nullable: true }) + mileageRegistrationHistory?: Array +} diff --git a/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts new file mode 100644 index 000000000000..a551c3dc893a --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/resolvers/bulkMileage.resolver.ts @@ -0,0 +1,84 @@ +import { Args, Mutation, Query, Resolver } from '@nestjs/graphql' +import { UseGuards } from '@nestjs/common' +import { + IdsUserGuard, + ScopesGuard, + Scopes, + CurrentUser, +} from '@island.is/auth-nest-tools' +import type { User } from '@island.is/auth-nest-tools' +import { ApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' +import { + FeatureFlagGuard, + FeatureFlag, + Features, +} from '@island.is/nest/feature-flags' +import { PostVehicleBulkMileageInput } from '../dto/postBulkVehicleMileage.input' +import { BulkMileageService } from '../services/bulkMileage.service' +import { VehiclesBulkMileageReadingResponse } from '../models/v3/bulkMileage/bulkMileageReadingResponse.model' +import { VehiclesBulkMileageRegistrationRequestOverview } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model' +import { VehiclesBulkMileageRegistrationJobHistory } from '../models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model' +import { VehiclesBulkMileageRegistrationRequestStatus } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model' +import { BulkVehicleMileageRequestStatusInput } from '../dto/getBulkVehicleMileageRequestStatus.input' +import { BulkVehicleMileageRequestOverviewInput } from '../dto/getBulkVehicleMileageRequestOverview.input' + +@UseGuards(IdsUserGuard, ScopesGuard, FeatureFlagGuard) +@FeatureFlag(Features.servicePortalVehicleBulkMileagePageEnabled) +@Resolver() +@Audit({ namespace: '@island.is/api/vehicles' }) +@Scopes(ApiScope.vehicles) +export class VehiclesBulkMileageResolver { + constructor(private readonly bulkService: BulkMileageService) {} + + @Query(() => VehiclesBulkMileageRegistrationJobHistory, { + name: 'vehicleBulkMileageRegistrationJobHistory', + nullable: true, + }) + @Audit() + getVehicleMileageRegistrationJobHistory(@CurrentUser() user: User) { + return this.bulkService.getBulkMileageRegistrationJobHistory(user) + } + + @Query(() => VehiclesBulkMileageRegistrationRequestStatus, { + name: 'vehicleBulkMileageRegistrationRequestStatus', + nullable: true, + }) + @Audit() + getVehicleMileageRegistrationRequestStatus( + @CurrentUser() user: User, + @Args('input') input: BulkVehicleMileageRequestStatusInput, + ) { + return this.bulkService.getBulkMileageRegistrationRequestStatus( + user, + input.requestId, + ) + } + + @Query(() => VehiclesBulkMileageRegistrationRequestOverview, { + name: 'vehicleBulkMileageRegistrationRequestOverview', + nullable: true, + }) + @Audit() + getVehicleMileageRegistrationRequestOverview( + @CurrentUser() user: User, + @Args('input') input: BulkVehicleMileageRequestOverviewInput, + ) { + return this.bulkService.getBulkMileageRegistrationRequestOverview( + user, + input.guid, + ) + } + + @Mutation(() => VehiclesBulkMileageReadingResponse, { + name: 'vehicleBulkMileagePost', + nullable: true, + }) + @Audit() + postBulkMileageReading( + @Args('input') input: PostVehicleBulkMileageInput, + @CurrentUser() user: User, + ) { + return this.bulkService.postBulkMileageReading(user, input) + } +} diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles-mileage.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/mileage.resolver.ts similarity index 95% rename from libs/api/domains/vehicles/src/lib/api-domains-vehicles-mileage.resolver.ts rename to libs/api/domains/vehicles/src/lib/resolvers/mileage.resolver.ts index c22385922c47..b95126c94212 100644 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles-mileage.resolver.ts +++ b/libs/api/domains/vehicles/src/lib/resolvers/mileage.resolver.ts @@ -17,7 +17,7 @@ import { import type { User } from '@island.is/auth-nest-tools' import { ApiScope } from '@island.is/auth/scopes' import { Audit } from '@island.is/nest/audit' -import { VehiclesService } from './api-domains-vehicles.service' +import { VehiclesService } from '../services/vehicles.service' import { VehicleMileageDetail, VehicleMileageOverview, @@ -88,9 +88,9 @@ export class VehiclesMileageResolver { mileage: Number(input.mileage ?? input.mileageNumber), }) - if (!res) return undefined + if (!res?.length) return undefined - return mileageDetailConstructor(res) + return mileageDetailConstructor(res[0]) } @ResolveField('canRegisterMileage', () => Boolean, { diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles-shared.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/shared.resolver.ts similarity index 95% rename from libs/api/domains/vehicles/src/lib/api-domains-vehicles-shared.resolver.ts rename to libs/api/domains/vehicles/src/lib/resolvers/shared.resolver.ts index 3feb413764ec..9b48d1f8f0c4 100644 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles-shared.resolver.ts +++ b/libs/api/domains/vehicles/src/lib/resolvers/shared.resolver.ts @@ -4,7 +4,7 @@ import type { Logger } from '@island.is/logging' import { IdsUserGuard, ScopesGuard } from '@island.is/auth-nest-tools' import type { User } from '@island.is/auth-nest-tools' import { Audit } from '@island.is/nest/audit' -import { VehiclesService } from './api-domains-vehicles.service' +import { VehiclesService } from '../services/vehicles.service' import { VehicleMileageDetail } from '../models/getVehicleMileage.model' import { VehiclesDetail } from '../models/getVehicleDetail.model' import { LOGGER_PROVIDER } from '@island.is/logging' diff --git a/libs/api/domains/vehicles/src/lib/resolvers/vehicleV3.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/vehicleV3.resolver.ts new file mode 100644 index 000000000000..6b90138ec10d --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/resolvers/vehicleV3.resolver.ts @@ -0,0 +1,48 @@ +import { Args, Query, Resolver } from '@nestjs/graphql' +import { UseGuards } from '@nestjs/common' +import { + IdsUserGuard, + ScopesGuard, + Scopes, + CurrentUser, +} from '@island.is/auth-nest-tools' +import type { User } from '@island.is/auth-nest-tools' +import { ApiScope } from '@island.is/auth/scopes' +import { Audit } from '@island.is/nest/audit' +import { VehiclesService } from '../services/vehicles.service' +import { VehiclesCurrentListResponse } from '../models/v3/currentVehicleListResponse.model' +import { VehiclesListInputV3 } from '../dto/vehiclesListInputV3' +import { MileageRegistrationHistory } from '../models/v3/mileageRegistrationHistory.model' +import { GetVehicleMileageInput } from '../dto/getVehicleMileageInput' + +@UseGuards(IdsUserGuard, ScopesGuard) +@Resolver(() => VehiclesCurrentListResponse) +@Audit({ namespace: '@island.is/api/vehicles' }) +export class VehiclesV3Resolver { + constructor(private readonly vehiclesService: VehiclesService) {} + + @Scopes(ApiScope.vehicles) + @Query(() => VehiclesCurrentListResponse, { + name: 'vehiclesListV3', + nullable: true, + }) + @Audit() + async getVehicleListV3( + @CurrentUser() user: User, + @Args('input', { nullable: true }) input: VehiclesListInputV3, + ) { + return this.vehiclesService.getVehiclesListV3(user, input) + } + @Scopes(ApiScope.vehicles) + @Query(() => MileageRegistrationHistory, { + name: 'vehiclesMileageRegistrationHistory', + nullable: true, + }) + @Audit() + async vehicleMileageRegistrations( + @CurrentUser() user: User, + @Args('input', { nullable: true }) input: GetVehicleMileageInput, + ) { + return this.vehiclesService.getVehicleMileageHistory(user, input) + } +} diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.resolver.ts b/libs/api/domains/vehicles/src/lib/resolvers/vehicles.resolver.ts similarity index 98% rename from libs/api/domains/vehicles/src/lib/api-domains-vehicles.resolver.ts rename to libs/api/domains/vehicles/src/lib/resolvers/vehicles.resolver.ts index 2998706d4684..2a92af484ca2 100644 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.resolver.ts +++ b/libs/api/domains/vehicles/src/lib/resolvers/vehicles.resolver.ts @@ -15,7 +15,6 @@ import { Audit } from '@island.is/nest/audit' import { DownloadServiceConfig } from '@island.is/nest/config' import type { ConfigType } from '@island.is/nest/config' import { VehiclesList, VehiclesListV2 } from '../models/usersVehicles.model' -import { VehiclesService } from './api-domains-vehicles.service' import { GetVehicleDetailInput } from '../dto/getVehicleDetailInput' import { VehiclesDetail, VehiclesExcel } from '../models/getVehicleDetail.model' import { VehiclesVehicleSearch } from '../models/getVehicleSearch.model' @@ -26,6 +25,7 @@ import { GetVehiclesListV2Input, } from '../dto/getVehiclesForUserInput' import { GetVehicleSearchInput } from '../dto/getVehicleSearchInput' +import { VehiclesService } from '../services/vehicles.service' const defaultCache: CacheControlOptions = { maxAge: CACHE_CONTROL_MAX_AGE } diff --git a/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts b/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts new file mode 100644 index 000000000000..72743c79f0ad --- /dev/null +++ b/libs/api/domains/vehicles/src/lib/services/bulkMileage.service.ts @@ -0,0 +1,143 @@ +import { Inject, Injectable } from '@nestjs/common' +import { + BulkMileageReadingRequestResultDto, + GetbulkmileagereadingrequeststatusGuidGetRequest, + MileageReadingApi, +} from '@island.is/clients/vehicles-mileage' +import { AuthMiddleware } from '@island.is/auth-nest-tools' +import type { Auth, User } from '@island.is/auth-nest-tools' +import { PostVehicleBulkMileageInput } from '../dto/postBulkVehicleMileage.input' +import { isDefined } from '@island.is/shared/utils' +import { LOG_CATEGORY } from '../constants' +import { LOGGER_PROVIDER, type Logger } from '@island.is/logging' +import { VehiclesBulkMileageReadingResponse } from '../models/v3/bulkMileage/bulkMileageReadingResponse.model' +import { VehiclesBulkMileageRegistrationJobHistory } from '../models/v3/bulkMileage/bulkMileageRegistrationJobHistory.model' +import { VehiclesBulkMileageRegistrationRequestStatus } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestStatus.model' +import { VehiclesBulkMileageRegistrationRequestOverview } from '../models/v3/bulkMileage/bulkMileageRegistrationRequestOverview.model' + +@Injectable() +export class BulkMileageService { + constructor( + private mileageReadingApi: MileageReadingApi, + @Inject(LOGGER_PROVIDER) private readonly logger: Logger, + ) {} + + private getMileageWithAuth(auth: Auth) { + return this.mileageReadingApi.withMiddleware(new AuthMiddleware(auth)) + } + + async postBulkMileageReading( + auth: User, + input: PostVehicleBulkMileageInput, + ): Promise { + if (!input) { + return null + } + + const res: BulkMileageReadingRequestResultDto = + await this.getMileageWithAuth(auth).requestbulkmileagereadingPost({ + postBulkMileageReadingModel: { + originCode: input.originCode, + mileageData: input.mileageData.map((m) => ({ + permno: m.vehicleId, + mileage: m.mileageNumber, + })), + }, + }) + + if (!res.guid) { + this.logger.warn( + 'Missing guid from bulk mileage reading registration response', + { + category: LOG_CATEGORY, + }, + ) + return null + } + + return { + requestId: res.guid, + errorMessage: res.errorMessage ?? undefined, + } + } + + async getBulkMileageRegistrationJobHistory( + auth: User, + ): Promise { + const res = await this.getMileageWithAuth( + auth, + ).getbulkmileagereadingrequestsGet({}) + + return { + history: res + .map((r) => { + if (!r.guid) { + return null + } + + return { + guid: r.guid, + reportingPersonNationalId: r.reportingPersidno ?? undefined, + reportingPersonName: r.reportingPersidnoName ?? undefined, + originCode: r.originCode ?? undefined, + originName: r.originName ?? undefined, + dateRequested: r.dateInserted ?? undefined, + dateStarted: r.dateStarted ?? undefined, + dateFinished: r.dateFinished ?? undefined, + } + }) + .filter(isDefined), + } + } + + async getBulkMileageRegistrationRequestStatus( + auth: User, + input: GetbulkmileagereadingrequeststatusGuidGetRequest['guid'], + ): Promise { + const data = await this.getMileageWithAuth( + auth, + ).getbulkmileagereadingrequeststatusGuidGet({ guid: input }) + + if (!data.guid) { + return null + } + + return { + requestId: data.guid, + jobsSubmitted: data.totalVehicles ?? undefined, + jobsFinished: data.done ?? undefined, + jobsRemaining: data.remaining ?? undefined, + jobsValid: data.processOk ?? undefined, + jobsErrored: data.processWithErrors ?? undefined, + } + } + + async getBulkMileageRegistrationRequestOverview( + auth: User, + input: GetbulkmileagereadingrequeststatusGuidGetRequest['guid'], + ): Promise { + const data = await this.getMileageWithAuth( + auth, + ).getbulkmileagereadingrequestdetailsGuidGet({ guid: input }) + + return { + requests: data + .map((d) => { + if (!d.guid || !d.permno) { + return null + } + return { + guid: d.guid, + vehicleId: d.permno, + mileage: d.mileage ?? undefined, + returnCode: d.returnCode ?? undefined, + errors: d.errors?.map((e) => ({ + code: e.errorCode ?? undefined, + message: e.errorText ?? undefined, + })), + } + }) + .filter(isDefined), + } + } +} diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.service.ts b/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts similarity index 75% rename from libs/api/domains/vehicles/src/lib/api-domains-vehicles.service.ts rename to libs/api/domains/vehicles/src/lib/services/vehicles.service.ts index cf7085d25cde..96488fed7ad6 100644 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.service.ts +++ b/libs/api/domains/vehicles/src/lib/services/vehicles.service.ts @@ -17,8 +17,8 @@ import { CanregistermileagePermnoGetRequest, GetMileageReadingRequest, MileageReadingApi, + MileageReadingDto, PostMileageReadingModel, - PutMileageReadingModel, RequiresmileageregistrationPermnoGetRequest, RootPostRequest, RootPutRequest, @@ -38,6 +38,11 @@ import { VehicleMileageOverview } from '../models/getVehicleMileage.model' import isSameDay from 'date-fns/isSameDay' import { mileageDetailConstructor } from '../utils/helpers' import { handle404 } from '@island.is/clients/middlewares' +import { VehiclesListInputV3 } from '../dto/vehiclesListInputV3' +import { VehiclesCurrentListResponse } from '../models/v3/currentVehicleListResponse.model' +import { isDefined } from '@island.is/shared/utils' +import { GetVehicleMileageInput } from '../dto/getVehicleMileageInput' +import { MileageRegistrationHistory } from '../models/v3/mileageRegistrationHistory.model' const ORIGIN_CODE = 'ISLAND.IS' const LOG_CATEGORY = 'vehicle-service' @@ -93,6 +98,61 @@ export class VehiclesService { }) } + async getVehiclesListV3( + auth: User, + input: VehiclesListInputV3, + ): Promise { + const res = await this.getVehiclesWithAuth( + auth, + ).currentvehicleswithmileageandinspGet({ + showCoowned: true, + showOperated: true, + showOwned: true, + page: input.page, + pageSize: input.pageSize, + }) + + if ( + !res.pageNumber || + !res.pageSize || + !res.totalPages || + !res.totalRecords + ) { + return null + } + + return { + pageNumber: res.pageNumber, + pageSize: res.pageSize, + totalPages: res.totalPages, + totalRecords: res.totalRecords, + data: + res.data + ?.map((d) => { + if ( + !d.permno || + !d.regno || + !d.canRegisterMilage || + !d.requiresMileageRegistration + ) { + return null + } + return { + vehicleId: d.permno, + registrationNumber: d.regno, + userRole: d.role ?? undefined, + type: d.make ?? undefined, + color: d.colorName ?? undefined, + mileageDetails: { + canRegisterMileage: d.canRegisterMilage, + requiresMileageRegistration: d.requiresMileageRegistration, + }, + } + }) + .filter(isDefined) ?? [], + } + } + async getVehiclesForUser( auth: User, input: GetVehiclesForUserInput, @@ -286,6 +346,52 @@ export class VehiclesService { } } + async getVehicleMileageHistory( + auth: User, + input: GetVehicleMileageInput, + ): Promise { + const res = await this.getMileageWithAuth(auth).getMileageReading({ + permno: input.permno, + }) + + const [lastRegistration, ...history] = res + + if (!lastRegistration.permno) { + return null + } + + return { + vehicleId: lastRegistration.permno, + lastMileageRegistration: + lastRegistration.originCode && + lastRegistration.readDate && + lastRegistration.mileage + ? { + originCode: lastRegistration.originCode, + mileage: lastRegistration.mileage, + date: lastRegistration.readDate, + } + : undefined, + mileageRegistrationHistory: history?.length + ? history + .map((h) => { + if (h.permno !== lastRegistration.permno) { + return null + } + if (!h.originCode || !h.mileage || !h.readDate) { + return null + } + return { + originCode: h.originCode, + mileage: h.mileage, + date: h.readDate, + } + }) + .filter(isDefined) + : undefined, + } + } + async postMileageReading( auth: User, input: RootPostRequest['postMileageReadingModel'], @@ -304,17 +410,24 @@ export class VehiclesService { throw new ForbiddenException(UNAUTHORIZED_OWNERSHIP_LOG) } - const res = await this.getMileageWithAuth(auth).rootPost({ + const res = await this.getMileageWithAuth(auth).rootPostRaw({ postMileageReadingModel: input, }) - return res + if (res.raw.status === 200) { + this.logger.info( + 'Tried to post already existing mileage reading. Should use PUT', + ) + return null + } + + return res.value() } async putMileageReading( auth: User, input: RootPutRequest['putMileageReadingModel'], - ): Promise { + ): Promise | null> { if (!input) return null const isAllowed = await this.isAllowedMileageRegistration( diff --git a/libs/api/domains/vehicles/src/utils/basicVehicleInformationMapper.ts b/libs/api/domains/vehicles/src/lib/utils/basicVehicleInformationMapper.ts similarity index 100% rename from libs/api/domains/vehicles/src/utils/basicVehicleInformationMapper.ts rename to libs/api/domains/vehicles/src/lib/utils/basicVehicleInformationMapper.ts diff --git a/libs/api/domains/vehicles/src/utils/helpers.ts b/libs/api/domains/vehicles/src/lib/utils/helpers.ts similarity index 100% rename from libs/api/domains/vehicles/src/utils/helpers.ts rename to libs/api/domains/vehicles/src/lib/utils/helpers.ts diff --git a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.module.ts b/libs/api/domains/vehicles/src/lib/vehicles.module.ts similarity index 52% rename from libs/api/domains/vehicles/src/lib/api-domains-vehicles.module.ts rename to libs/api/domains/vehicles/src/lib/vehicles.module.ts index 49874c35352a..3fcb00edafd3 100644 --- a/libs/api/domains/vehicles/src/lib/api-domains-vehicles.module.ts +++ b/libs/api/domains/vehicles/src/lib/vehicles.module.ts @@ -2,19 +2,25 @@ import { Module } from '@nestjs/common' import { VehiclesClientModule } from '@island.is/clients/vehicles' import { VehiclesMileageClientModule } from '@island.is/clients/vehicles-mileage' -import { VehiclesResolver } from './api-domains-vehicles.resolver' -import { VehiclesMileageResolver } from './api-domains-vehicles-mileage.resolver' -import { VehiclesService } from './api-domains-vehicles.service' +import { VehiclesResolver } from './resolvers/vehicles.resolver' +import { VehiclesMileageResolver } from './resolvers/mileage.resolver' +import { VehiclesService } from './services/vehicles.service' import { AuthModule } from '@island.is/auth-nest-tools' -import { VehiclesSharedResolver } from './api-domains-vehicles-shared.resolver' +import { VehiclesSharedResolver } from './resolvers/shared.resolver' import { FeatureFlagModule } from '@island.is/nest/feature-flags' +import { BulkMileageService } from './services/bulkMileage.service' +import { VehiclesV3Resolver } from './resolvers/vehicleV3.resolver' +import { VehiclesBulkMileageResolver } from './resolvers/bulkMileage.resolver' @Module({ providers: [ VehiclesResolver, VehiclesSharedResolver, VehiclesMileageResolver, + VehiclesBulkMileageResolver, + VehiclesV3Resolver, VehiclesService, + BulkMileageService, ], imports: [ VehiclesClientModule, diff --git a/libs/clients/vehicles-mileage/project.json b/libs/clients/vehicles-mileage/project.json index ef161d547864..5ae8a04b0eca 100644 --- a/libs/clients/vehicles-mileage/project.json +++ b/libs/clients/vehicles-mileage/project.json @@ -21,10 +21,11 @@ "commands": [ "curl -H \"X-Road-Client: IS-DEV/GOV/10000/island-is-client\" http://localhost:8081/r1/IS-DEV/GOV/10017/Samgongustofa-Protected/getOpenAPI?serviceCode=Vehicle-Mileagereading-V1 > src/clientConfig.json", "jq '.components.schemas.ProblemDetails.additionalProperties = false' src/{args.apiVersion}/clientConfig.json > _.tmp && mv _.tmp src/{args.apiVersion}/clientConfig.json", + "cat <<< $(jq 'del(.paths.\"/\".post.responses.\"200\")' src/clientConfig.json) > src/clientConfig.json", "prettier --write src/clientConfig.json" ], "parallel": false, - "cwd": "libs/clients/vehicles" + "cwd": "libs/clients/vehicles-mileage" } }, "codegen/backend-client": { diff --git a/libs/clients/vehicles-mileage/src/clientConfig.json b/libs/clients/vehicles-mileage/src/clientConfig.json index 75a8201878cb..24dee803a1d7 100644 --- a/libs/clients/vehicles-mileage/src/clientConfig.json +++ b/libs/clients/vehicles-mileage/src/clientConfig.json @@ -2,14 +2,13 @@ "openapi": "3.0.1", "info": { "title": "SGS Rest API", - "description": "Mileage reading API developed in .Net6.0 - Release-5 : 20231122.2", + "description": "Mileage reading API developed in .Net8.0 - Release-6 : 20231122.2", "contact": { "name": "Samgöngustofa", "email": "tolvuhjalp@samgongustofa.is" }, "version": "1.0" }, - "servers": [{ "url": "/vehicle/mileagereading" }], "paths": { "/authenticate": { "post": { @@ -55,7 +54,7 @@ }, "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "text/plain": { "schema": { "$ref": "#/components/schemas/User" } @@ -177,6 +176,14 @@ "schema": { "$ref": "#/components/schemas/ProblemDetails" } } } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } } } }, @@ -237,11 +244,12 @@ }, "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/PutMileageReadingModel" + "type": "array", + "items": { "$ref": "#/components/schemas/MileageReadingDto" } } } } @@ -291,7 +299,7 @@ ], "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "application/json": { "schema": { @@ -300,6 +308,22 @@ } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } } } } @@ -329,12 +353,27 @@ ], "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "application/json": { "schema": { "type": "string" } } } }, - "204": { "description": "No Content" } + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + } } } }, @@ -363,10 +402,26 @@ ], "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "application/json": { "schema": { "type": "boolean" } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } } } } @@ -396,10 +451,290 @@ ], "responses": { "200": { - "description": "Success", + "description": "OK", "content": { "application/json": { "schema": { "type": "boolean" } } } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + } + } + } + }, + "/requestbulkmileagereading": { + "post": { + "tags": ["MileageReading"], + "parameters": [ + { + "name": "api-version", + "in": "header", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + }, + { + "name": "api-version", + "in": "query", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + } + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "text/json": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "application/*+json": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "text/xml": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + }, + "application/*+xml": { + "schema": { + "$ref": "#/components/schemas/PostBulkMileageReadingModel" + } + } + } + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkMileageReadingRequestResultDto" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkMileageReadingRequestResultDto" + } + } + } + } + } + } + }, + "/getbulkmileagereadingrequests": { + "get": { + "tags": ["MileageReading"], + "parameters": [ + { "name": "persidno", "in": "query", "schema": { "type": "string" } }, + { + "name": "api-version", + "in": "header", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + }, + { + "name": "api-version", + "in": "query", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkMileageReadingRequestDto" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + } + } + } + }, + "/getbulkmileagereadingrequestdetails/{guid}": { + "get": { + "tags": ["MileageReading"], + "parameters": [ + { + "name": "guid", + "in": "path", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "api-version", + "in": "header", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + }, + { + "name": "api-version", + "in": "query", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkMileageReadingRequestDetailDto" + } + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + } + } + } + }, + "/getbulkmileagereadingrequeststatus/{guid}": { + "get": { + "tags": ["MileageReading"], + "parameters": [ + { + "name": "guid", + "in": "path", + "required": true, + "schema": { "type": "string" } + }, + { + "name": "api-version", + "in": "header", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + }, + { + "name": "api-version", + "in": "query", + "description": "The requested API version", + "schema": { "type": "string", "default": "1.0" } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BulkMileageReadingStatusDto" + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProblemDetails" } + } + } } } } @@ -416,6 +751,160 @@ }, "additionalProperties": false }, + "BulkMileageReadingRequestDetailDto": { + "type": "object", + "properties": { + "guid": { "type": "string", "description": "Guid", "nullable": true }, + "permno": { + "type": "string", + "description": "Vehicle permanent number", + "nullable": true + }, + "mileage": { + "type": "integer", + "description": "Mileage", + "format": "int32", + "nullable": true + }, + "returnCode": { + "type": "string", + "description": "Return code", + "nullable": true + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BulkMileageReadingRequestDetailErrors" + }, + "description": "List of errors if any", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Dto for bulk mileage reading request details" + }, + "BulkMileageReadingRequestDetailErrors": { + "type": "object", + "properties": { + "errorCode": { + "type": "string", + "description": "Error code", + "nullable": true + }, + "errorText": { + "type": "string", + "description": "Error text", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class for errors on mileage reading in bulk" + }, + "BulkMileageReadingRequestDto": { + "type": "object", + "properties": { + "guid": { "type": "string", "description": "Guid", "nullable": true }, + "reportingPersidno": { + "type": "string", + "description": "Reporting person social security number", + "nullable": true + }, + "reportingPersidnoName": { + "type": "string", + "description": "Reporting person name", + "nullable": true + }, + "originCode": { + "type": "string", + "description": "Origin code", + "nullable": true + }, + "originName": { + "type": "string", + "description": "Origin name", + "nullable": true + }, + "dateInserted": { + "type": "string", + "description": "When was bulk request inserted", + "format": "date-time", + "nullable": true + }, + "dateStarted": { + "type": "string", + "description": "When did bulk request start working", + "format": "date-time", + "nullable": true + }, + "dateFinished": { + "type": "string", + "description": "When dd bulk request finish", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Dto for bulk mileage reading requests" + }, + "BulkMileageReadingRequestResultDto": { + "type": "object", + "properties": { + "guid": { + "type": "string", + "description": "Guid to check for status and results", + "nullable": true + }, + "errorMessage": { + "type": "string", + "description": "Error message if any", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Dto for bulk mileage reading request" + }, + "BulkMileageReadingStatusDto": { + "type": "object", + "properties": { + "guid": { + "type": "string", + "description": "Guid of request", + "nullable": true + }, + "totalVehicles": { + "type": "integer", + "description": "Total vehicles in the request", + "format": "int32", + "nullable": true + }, + "done": { + "type": "integer", + "description": "Total done", + "format": "int32", + "nullable": true + }, + "remaining": { + "type": "integer", + "description": "Remaining vehicles", + "format": "int32", + "nullable": true + }, + "processOk": { + "type": "integer", + "description": "How many readings were ok", + "format": "int32", + "nullable": true + }, + "processWithErrors": { + "type": "integer", + "description": "How many readings with errors/warnings/locks", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Dto for status of bulk mileage reading request" + }, "MileageReadingDto": { "type": "object", "properties": { @@ -449,11 +938,57 @@ "type": "string", "description": "Operation (I,U,D)", "nullable": true + }, + "reportingPersidno": { + "type": "string", + "description": "If user registering this mileage is not owner/operator, the owner/operator social security number should be in here", + "nullable": true + }, + "transactionDate": { + "type": "string", + "description": "Date of transaction", + "format": "date-time", + "nullable": true } }, "additionalProperties": false, "description": "Mileage data transfer object" }, + "MileageReadingModel": { + "type": "object", + "properties": { + "permno": { + "type": "string", + "description": "Vehicle permno", + "nullable": true + }, + "mileage": { + "type": "integer", + "description": "Milegae", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "One mileage entity" + }, + "PostBulkMileageReadingModel": { + "type": "object", + "properties": { + "originCode": { + "type": "string", + "description": "Origin code of reading", + "nullable": true + }, + "mileageData": { + "type": "array", + "items": { "$ref": "#/components/schemas/MileageReadingModel" }, + "description": "Mileage reading data", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Bulk mileage reading model" + }, "PostMileageReadingModel": { "required": ["mileage", "originCode", "permno"], "type": "object", @@ -490,6 +1025,14 @@ "description": "Operation (I,U,D), is always I for Insert", "nullable": true, "readOnly": true + }, + "reportingPersidno": { + "maxLength": 10, + "minLength": 10, + "pattern": "[0-7]\\d[01]\\d{3}[-]*\\d{3}[09]", + "type": "string", + "description": "If user registering this mileage is not owner/operator, the owner/operator social security number should be in here", + "nullable": true } }, "additionalProperties": false, diff --git a/libs/feature-flags/src/lib/features.ts b/libs/feature-flags/src/lib/features.ts index fd67ac6db300..acefa400d386 100644 --- a/libs/feature-flags/src/lib/features.ts +++ b/libs/feature-flags/src/lib/features.ts @@ -50,6 +50,7 @@ export enum Features { servicePortalWorkMachinesModule = 'isServicePortalWorkMachinesPageEnabled', servicePortalSignatureCollection = 'isServicePortalSignatureCollectionEnabled', servicePortalVehicleMileagePageEnabled = 'isServicePortalVehicleMileagePageEnabled', + servicePortalVehicleBulkMileagePageEnabled = 'isServicePortalVehicleBulkMileagePageEnabled', servicePortalSocialInsurancePageEnabled = 'isServicePortalSocialInsurancePageEnabled', servicePortalSocialInsuranceIncomePlanPageEnabled = 'isServicePortalSocialInsuranceIncomePlanPageEnabled', diff --git a/libs/island-ui/core/src/lib/Icon/Icon.tsx b/libs/island-ui/core/src/lib/Icon/Icon.tsx index 2e2af21e865f..e050b463c3f8 100644 --- a/libs/island-ui/core/src/lib/Icon/Icon.tsx +++ b/libs/island-ui/core/src/lib/Icon/Icon.tsx @@ -30,6 +30,7 @@ export type IconTypes = | 'user' | 'calendar' | 'download' + | 'upload' | 'toasterSuccess' | 'toasterInfo' | 'toasterError' @@ -242,6 +243,12 @@ const iconsConf: Icons = { width: 24, path: 'M21.3333 12V21.3333H2.66667V12H0V21.3333C0 22.8 1.2 24 2.66667 24H21.3333C22.8 24 24 22.8 24 21.3333V12H21.3333ZM13.3333 12.8933L16.7867 9.45333L18.6667 11.3333L12 18L5.33333 11.3333L7.21333 9.45333L10.6667 12.8933V0H13.3333V12.8933Z', }, + upload: { + viewBox: '0 0 24 24', + height: 24, + width: 24, + path: 'M7.5 14.5C7.5 14.7761 7.72386 15 8 15C8.27614 15 8.5 14.7761 8.5 14.5V6.20711L10.1464 7.85355C10.3417 8.04882 10.6583 8.04882 10.8536 7.85355C11.0488 7.65829 11.0488 7.34171 10.8536 7.14645L8.35355 4.64645C8.15829 4.45119 7.84171 4.45119 7.64645 4.64645L5.14645 7.14645C4.95118 7.34171 4.95118 7.65829 5.14645 7.85355C5.34171 8.04882 5.65829 8.04882 5.85355 7.85355L7.5 6.20711L7.5 14.5Z', + }, } const SvgPathContainer = ({ diff --git a/libs/island-ui/core/src/lib/IconRC/iconMap.ts b/libs/island-ui/core/src/lib/IconRC/iconMap.ts index e3e549ffa12b..c562731e4731 100644 --- a/libs/island-ui/core/src/lib/IconRC/iconMap.ts +++ b/libs/island-ui/core/src/lib/IconRC/iconMap.ts @@ -32,6 +32,7 @@ export type Icon = | 'documents' | 'dots' | 'download' + | 'upload' | 'ellipse' | 'ellipsisHorizontal' | 'ellipsisVertical' @@ -126,6 +127,7 @@ export default { documents: 'Documents', dots: 'Dots', download: 'Download', + upload: 'Upload', ellipse: 'Ellipse', ellipsisHorizontal: 'EllipsisHorizontal', ellipsisVertical: 'EllipsisVertical', @@ -219,6 +221,7 @@ export default { documents: 'DocumentsOutline', dots: 'Dots', download: 'DownloadOutline', + upload: 'UploadOutline', ellipse: 'EllipseOutline', ellipsisHorizontal: 'EllipsisHorizontalOutline', ellipsisVertical: 'EllipsisVerticalOutline', diff --git a/libs/island-ui/core/src/lib/IconRC/icons/Upload.tsx b/libs/island-ui/core/src/lib/IconRC/icons/Upload.tsx new file mode 100644 index 000000000000..c45fa30f8181 --- /dev/null +++ b/libs/island-ui/core/src/lib/IconRC/icons/Upload.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import type { SvgProps as SVGRProps } from '../types' + +const SvgUpload = ({ + title, + titleId, + ...props +}: React.SVGProps & SVGRProps) => { + return ( + + {title ? {title} : null} + + + ) +} + +export default SvgUpload diff --git a/libs/island-ui/core/src/lib/IconRC/icons/UploadOutline.tsx b/libs/island-ui/core/src/lib/IconRC/icons/UploadOutline.tsx new file mode 100644 index 000000000000..cbc866c40a3f --- /dev/null +++ b/libs/island-ui/core/src/lib/IconRC/icons/UploadOutline.tsx @@ -0,0 +1,38 @@ +import * as React from 'react' +import type { SvgProps as SVGRProps } from '../types' + +const SvgUploadOutline = ({ + title, + titleId, + ...props +}: React.SVGProps & SVGRProps) => { + return ( + + {title ? {title} : null} + + + + ) +} + +export default SvgUploadOutline diff --git a/libs/service-portal/assets/src/lib/messages.ts b/libs/service-portal/assets/src/lib/messages.ts index ef01b45773f5..5a9329ee69f5 100644 --- a/libs/service-portal/assets/src/lib/messages.ts +++ b/libs/service-portal/assets/src/lib/messages.ts @@ -283,6 +283,10 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:permno', defaultMessage: 'Fastanúmer', }, + lastRegistration: { + id: 'sp.vehicles:last-registration', + defaultMessage: 'Síðasta skráning', + }, verno: { id: 'sp.vehicles:verno', defaultMessage: 'Verksmiðjunúmer', @@ -391,6 +395,14 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:insp-type', defaultMessage: 'Tegund skoðunar', }, + registration: { + id: 'sp.vehicles:registration', + defaultMessage: 'Skráning', + }, + annualUsage: { + id: 'sp.vehicles:annual-usage', + defaultMessage: 'Ársnotkun', + }, date: { id: 'sp.vehicles:date', defaultMessage: 'Dagsetning', @@ -880,6 +892,163 @@ export const vehicleMessage = defineMessages({ id: 'sp.vehicles:mileage-external-link', defaultMessage: '/kilometragjald-a-vetnis-og-rafmagnsbila', }, + bulkMileageButton: { + id: 'sp.vehicles:bulk-mileage-btn', + defaultMessage: 'Senda inn gögn', + }, + bulkMileageUploadStatus: { + id: 'sp.vehicles:bulk-mileage-upload-status', + defaultMessage: 'Skoða má stöðu upphleðslu á magnskráningarsíðu', + }, + bulkPostMileage: { + id: 'sp.vehicles:bulk-post-mileage', + defaultMessage: 'Magnskrá kílómetrastöðu', + }, + jobOverview: { + id: 'sp.vehicles:job-overview', + defaultMessage: 'Yfirlit skráninga', + }, + jobsSubmitted: { + id: 'sp.vehicles:jobs-submitted', + defaultMessage: 'Innsendar kílómetrastöðuskráningar', + }, + jobSubmitted: { + id: 'sp.vehicles:job-submitted', + defaultMessage: 'Innsending', + }, + jobStarted: { + id: 'sp.vehicles:job-started', + defaultMessage: 'Verk hófst', + }, + jobFinished: { + id: 'sp.vehicles:job-finished', + defaultMessage: 'Verki lauk', + }, + jobNotStarted: { + id: 'sp.vehicles:job-not-started', + defaultMessage: 'Ekki hafið', + }, + openJob: { + id: 'sp.vehicles:open-job', + defaultMessage: 'Opna keyrslu', + }, + jobStatus: { + id: 'sp.vehicles:job-status', + defaultMessage: 'Staða keyrslu', + }, + jobInProgress: { + id: 'sp.vehicles:job-in-progress', + defaultMessage: 'Í vinnslu', + }, + goToJob: { + id: 'sp.vehicles:go-to-job', + defaultMessage: 'Skoða verk', + }, + noJobFound: { + id: 'sp.vehicles:no-job-found', + defaultMessage: 'Ekkert verk fannst', + }, + noJobsFound: { + id: 'sp.vehicles:no-jobs-found', + defaultMessage: 'Engin verk fundust', + }, + uploadFailed: { + id: 'sp.vehicles:upload-failed', + defaultMessage: 'Upphleðsla mistókst', + }, + errorWhileProcessing: { + id: 'sp.vehicles:error-while-processing', + defaultMessage: 'Villa við að meðhöndla skjal. Villur: ', + }, + downloadFailed: { + id: 'sp.vehicles:download-failed', + defaultMessage: 'Niðurhal mistókst', + }, + uploadSuccess: { + id: 'sp.vehicles:upload-success', + defaultMessage: 'Upphleðsla tókst', + }, + totalSubmitted: { + id: 'sp.vehicles:total-submitted', + defaultMessage: 'Fjöldi innsendra', + }, + totalFinished: { + id: 'sp.vehicles:total-finished', + defaultMessage: 'Fjöldi lokið', + }, + totalRemaining: { + id: 'sp.vehicles:total-remaining', + defaultMessage: 'Fjöldi eftir', + }, + healthyJobs: { + id: 'sp.vehicles:healthy-jobs', + defaultMessage: 'Heilbrigð verk', + }, + unhealthyJobs: { + id: 'sp.vehicles:unhealthy-jobs', + defaultMessage: 'Misheppnuð verk', + }, + noValidMileage: { + id: 'sp.vehicles:no-valid-mileage', + defaultMessage: 'Engin gild kílómetrastaða fannst í skjali', + }, + dragFileToUpload: { + id: 'sp.vehicles:drag-file-to-upload', + defaultMessage: 'Dragðu skjal hingað til að hlaða upp', + }, + errors: { + id: 'sp.vehicles:errors', + defaultMessage: 'Villur', + }, + noRegistrationsFound: { + id: 'sp.vehicles:no-registrations-found', + defaultMessage: 'Engar skráningar fundust', + }, + downloadErrors: { + id: 'sp.vehicles:download-errors', + defaultMessage: 'Hlaða niður villum (.csv)', + }, + fileUploadAcceptedTypes: { + id: 'sp.vehicles:file-upload-accepted-types', + defaultMessage: 'Tekið er við skjölum með endingu; .csv, .xls', + }, + dataAboutJob: { + id: 'sp.vehicles:data-about-job', + defaultMessage: 'Hér finnur þú upplýsingar um skráningu', + }, + refreshDataAboutJob: { + id: 'sp.vehicles:refresh-data-about-job', + defaultMessage: + 'Til að sækja nýjustu stöðu er hægt að smella á "Uppfæra stöðu"', + }, + refreshJob: { + id: 'sp.vehicles:refresh-job', + defaultMessage: 'Uppfæra stöðu', + }, + mileageHistoryFetchFailed: { + id: 'sp.vehicles:mileage-history-fetch-failed', + defaultMessage: 'Eitthvað fór úrskeiðis við að sækja fyrri skráningar', + }, + mileageHistoryNotFound: { + id: 'sp.vehicles:mileage-history-not-found', + defaultMessage: 'Engar fyrri skráningar fundust', + }, + selectFileToUpload: { + id: 'sp.vehicles:select-file-to-upload', + defaultMessage: 'Velja skjal til að hlaða upp', + }, + downloadTemplate: { + id: 'sp.vehicles:download-template', + defaultMessage: 'Hlaða niður sniðmáti', + }, + saveAllVisible: { + id: 'sp.vehicles:save-all-visible', + defaultMessage: 'Vista allar sýnilegar færslur', + }, + entriesPerPage: { + id: 'sp.vehicles:entries-per-page', + defaultMessage: 'Fj. á síðu:', + }, }) export const ipMessages = defineMessages({ diff --git a/libs/service-portal/assets/src/lib/navigation.ts b/libs/service-portal/assets/src/lib/navigation.ts index 4a91891aef71..5c9e98a530b7 100644 --- a/libs/service-portal/assets/src/lib/navigation.ts +++ b/libs/service-portal/assets/src/lib/navigation.ts @@ -48,6 +48,29 @@ export const assetsNavigation: PortalNavigationItem = { name: m.vehiclesLookup, path: AssetsPaths.AssetsVehiclesLookup, }, + { + name: m.vehiclesBulkMileage, + path: AssetsPaths.AssetsVehiclesBulkMileage, + children: [ + { + name: m.vehiclesBulkMileageUpload, + path: AssetsPaths.AssetsVehiclesBulkMileageUpload, + navHide: true, + }, + { + name: m.vehiclesBulkMileageJobOverview, + path: AssetsPaths.AssetsVehiclesBulkMileageJobOverview, + navHide: true, + children: [ + { + name: m.vehiclesBulkMileageJobDetail, + path: AssetsPaths.AssetsVehiclesBulkMileageJobDetail, + navHide: true, + }, + ], + }, + ], + }, { name: m.vehiclesHistory, path: AssetsPaths.AssetsVehiclesHistory, diff --git a/libs/service-portal/assets/src/lib/paths.ts b/libs/service-portal/assets/src/lib/paths.ts index cc56bd55eb1f..f784fe122679 100644 --- a/libs/service-portal/assets/src/lib/paths.ts +++ b/libs/service-portal/assets/src/lib/paths.ts @@ -6,6 +6,10 @@ export enum AssetsPaths { AssetsMyVehicles = '/eignir/okutaeki/min-okutaeki', AssetsVehiclesDetail = '/eignir/okutaeki/min-okutaeki/:id', AssetsVehiclesDetailMileage = '/eignir/okutaeki/min-okutaeki/:id/kilometrastada', + AssetsVehiclesBulkMileage = '/eignir/okutaeki/magnskraning-kilometrastodu', + AssetsVehiclesBulkMileageUpload = '/eignir/okutaeki/magnskraning-kilometrastodu/hlada-upp', + AssetsVehiclesBulkMileageJobOverview = '/eignir/okutaeki/magnskraning-kilometrastodu/runuverk', + AssetsVehiclesBulkMileageJobDetail = '/eignir/okutaeki/magnskraning-kilometrastodu/runuverk/:id', AssetsVehiclesLookup = '/eignir/okutaeki/leit', AssetsVehiclesHistory = '/eignir/okutaeki/okutaekjaferill', AssetsWorkMachines = '/eignir/vinnuvelar', diff --git a/libs/service-portal/assets/src/module.tsx b/libs/service-portal/assets/src/module.tsx index 633e5ac1b7cd..6237c03c3fca 100644 --- a/libs/service-portal/assets/src/module.tsx +++ b/libs/service-portal/assets/src/module.tsx @@ -54,6 +54,23 @@ const VehicleMileage = lazy(() => import('./screens/VehicleMileage/VehicleMileage'), ) +const VehicleBulkMileage = lazy(() => + import('./screens/VehicleBulkMileage/VehicleBulkMileage'), +) + +const VehicleBulkMileageUpload = lazy(() => + import('./screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload'), +) + +const VehicleBulkMileageJobOverview = lazy(() => + import( + './screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview' + ), +) +const VehicleBulkMileageJobDetail = lazy(() => + import('./screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail'), +) + export const assetsModule: PortalModule = { name: 'Fasteignir', routes: ({ userInfo, ...rest }) => { @@ -128,6 +145,34 @@ export const assetsModule: PortalModule = { key: 'VehicleMileage', element: , }, + { + name: m.vehiclesBulkMileage, + path: AssetsPaths.AssetsVehiclesBulkMileage, + enabled: userInfo.scopes.includes(ApiScope.vehicles), + key: 'VehicleBulkMileage', + element: , + }, + { + name: m.vehiclesBulkMileageUpload, + path: AssetsPaths.AssetsVehiclesBulkMileageUpload, + enabled: userInfo.scopes.includes(ApiScope.vehicles), + key: 'VehicleBulkMileage', + element: , + }, + { + name: m.vehiclesBulkMileageJobOverview, + path: AssetsPaths.AssetsVehiclesBulkMileageJobOverview, + enabled: userInfo.scopes.includes(ApiScope.vehicles), + key: 'VehicleBulkMileage', + element: , + }, + { + name: m.vehiclesBulkMileageJobDetail, + path: AssetsPaths.AssetsVehiclesBulkMileageJobDetail, + enabled: userInfo.scopes.includes(ApiScope.vehicles), + key: 'VehicleBulkMileage', + element: , + }, { name: m.vehiclesLookup, path: AssetsPaths.AssetsVehiclesLookup, diff --git a/libs/service-portal/assets/src/screens/IntellectualPropertiesPatentDetail/patentVariations/IS.tsx b/libs/service-portal/assets/src/screens/IntellectualPropertiesPatentDetail/patentVariations/IS.tsx index 533e79a08d45..a4980f64b2c4 100644 --- a/libs/service-portal/assets/src/screens/IntellectualPropertiesPatentDetail/patentVariations/IS.tsx +++ b/libs/service-portal/assets/src/screens/IntellectualPropertiesPatentDetail/patentVariations/IS.tsx @@ -8,7 +8,6 @@ import { } from '@island.is/service-portal/core' import { IntellectualPropertiesPatentIs } from '@island.is/api/schema' import { Divider, Stack, Text } from '@island.is/island-ui/core' -import { Problem } from '@island.is/react-spa/shared' import { ipMessages } from '../../../lib/messages' import { useMemo } from 'react' import Timeline from '../../../components/Timeline/Timeline' diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.css.ts b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.css.ts new file mode 100644 index 000000000000..81c4758f83c3 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.css.ts @@ -0,0 +1,11 @@ +import { theme } from '@island.is/island-ui/theme' +import { style } from '@vanilla-extract/css' + +export const link = style({ + color: theme.color.blue400, + textDecoration: 'underline', +}) + +export const mwInput = style({ + maxWidth: 150, +}) diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.graphql b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.graphql new file mode 100644 index 000000000000..9b71ade607a2 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.graphql @@ -0,0 +1,37 @@ +query vehiclesList($input: VehiclesListInputV3!) { + vehiclesListV3(input: $input) { + pageNumber + pageSize + totalPages + totalRecords + data { + vehicleId + registrationNumber + userRole + type + color + mileageDetails { + canRegisterMileage + requiresMileageRegistration + } + } + } +} + +fragment mileageRegistration on VehiclesMileageRegistration { + originCode + mileage + date +} + +query vehicleMileageRegistrationHistory($input: GetVehicleMileageInput) { + vehiclesMileageRegistrationHistory(input: $input) { + vehicleId + lastMileageRegistration { + ...mileageRegistration + } + mileageRegistrationHistory { + ...mileageRegistration + } + } +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx new file mode 100644 index 000000000000..aa4984c4e216 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileage.tsx @@ -0,0 +1,146 @@ +import { Stack, Pagination, Text, Inline } from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { + m, + SAMGONGUSTOFA_SLUG, + IntroHeader, + LinkButton, +} from '@island.is/service-portal/core' +import { vehicleMessage as messages, vehicleMessage } from '../../lib/messages' +import * as styles from './VehicleBulkMileage.css' +import { useEffect, useState } from 'react' +import VehicleBulkMileageTable from './VehicleBulkMileageTable' +import { SubmissionState, VehicleType } from './types' +import { FormProvider, useForm } from 'react-hook-form' +import { useVehiclesListQuery } from './VehicleBulkMileage.generated' +import { isDefined } from '@island.is/shared/utils' +import { AssetsPaths } from '../../lib/paths' +import { Problem } from '@island.is/react-spa/shared' + +interface FormData { + [key: string]: number +} + +const VehicleBulkMileage = () => { + useNamespaces('sp.vehicles') + const { formatMessage } = useLocale() + const [vehicles, setVehicles] = useState>([]) + const [page, setPage] = useState(1) + const [totalPages, setTotalPages] = useState(1) + const [pageSize, setPageSize] = useState(10) + + const { data, loading, error, fetchMore } = useVehiclesListQuery({ + variables: { + input: { + page, + pageSize, + }, + }, + }) + + const methods = useForm() + + useEffect(() => { + if (data?.vehiclesListV3?.data) { + const vehicles: Array = data.vehiclesListV3?.data + .map((v) => { + if (!v.type) { + return null + } + return { + vehicleId: v.vehicleId, + vehicleType: v.type, + submissionStatus: 'idle' as const, + lastMileageRegistration: undefined, + } + }) + .filter(isDefined) + setVehicles(vehicles) + setTotalPages(data?.vehiclesListV3?.totalPages || 1) + } + }, [data?.vehiclesListV3]) + + const updateVehicleStatus = (status: SubmissionState, vehicleId: string) => { + const newVehicles = vehicles.map((v) => { + if (v.vehicleId === vehicleId) { + return { + ...v, + submissionStatus: status, + } + } else return v + }) + setVehicles(newVehicles) + } + + return ( + + + + {formatMessage(messages.vehicleMileageIntro, { + href: (str: React.ReactNode) => ( + + + {str} + + + ), + })} + + } + serviceProviderSlug={SAMGONGUSTOFA_SLUG} + serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} + /> + + + + + + {error && !loading && } + {!error && ( + + )} + + {totalPages > 1 && ( + ( + + )} + /> + )} + + + + ) +} + +export default VehicleBulkMileage diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageFileDownloader.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageFileDownloader.tsx new file mode 100644 index 000000000000..e9cb958cc1d3 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageFileDownloader.tsx @@ -0,0 +1,46 @@ +import { Button } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { vehicleMessage } from '../../lib/messages' +import { downloadFile } from '@island.is/service-portal/core' +import { useState } from 'react' + +interface Props { + onError: (error: string) => void +} + +const VehicleBulkMileageFileDownloader = ({ onError }: Props) => { + const { formatMessage } = useLocale() + const [isLoading, setIsLoading] = useState(false) + + const downloadExampleFile = async () => { + setIsLoading(true) + try { + downloadFile( + `magnskraning_kilometrastodu_example`, + ['Ökutæki', 'Kílómetrastaða'], + [['ABC001', 10000]], + 'csv', + ) + } catch (error) { + onError(error) + } finally { + setIsLoading(false) + } + } + + return ( + + ) +} + +export default VehicleBulkMileageFileDownloader diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx new file mode 100644 index 000000000000..8ca205d54aa4 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageRow.tsx @@ -0,0 +1,160 @@ +import { AlertMessage, Box } from '@island.is/island-ui/core' +import { + ExpandRow, + NestedFullTable, + formatDate, +} from '@island.is/service-portal/core' +import * as styles from './VehicleBulkMileage.css' +import { VehicleBulkMileageSaveButton } from './VehicleBulkMileageSaveButton' +import { useLocale } from '@island.is/localization' +import { vehicleMessage } from '../../lib/messages' +import { InputController } from '@island.is/shared/form-fields' +import { useFormContext } from 'react-hook-form' +import { VehicleType } from './types' +import { isReadDateToday } from '../../utils/readDate' +import { useVehicleMileageRegistrationHistoryLazyQuery } from './VehicleBulkMileage.generated' +import { displayWithUnit } from '../../utils/displayWithUnit' + +interface Props { + vehicle: VehicleType + onSave: (vehicleId: string) => void +} + +export const VehicleBulkMileageRow = ({ vehicle, onSave }: Props) => { + const { formatMessage } = useLocale() + + const [executeRegistrationsQuery, { data, loading, error }] = + useVehicleMileageRegistrationHistoryLazyQuery({ + variables: { + input: { + permno: vehicle.vehicleId, + }, + }, + }) + + const { + control, + formState: { errors }, + } = useFormContext() + + const onSaveButtonClick = () => { + onSave(vehicle.vehicleId) + } + + return ( + + { + // Input number must be higher than the highest known mileage registration value + if (vehicle.registrationHistory) { + // If we're in editing mode, we want to find the highest confirmed registered number, ignoring all Island.is registrations from today. + const confirmedRegistrations = + vehicle.registrationHistory.filter((item) => { + if (item.date) { + const isIslandIsReadingToday = + item.origin === 'ISLAND-IS' && + isReadDateToday(new Date(item.date)) + return !isIslandIsReadingToday + } + return true + }) + + const detailArray = vehicle.isCurrentlyEditing + ? confirmedRegistrations + : [...vehicle.registrationHistory] + + const latestRegistration = + detailArray.length > 0 ? detailArray?.[0].mileage : 0 + if (latestRegistration > value) { + return formatMessage( + vehicleMessage.mileageInputTooLow, + ) + } + } + }, + }, + minLength: { + value: 1, + message: formatMessage( + vehicleMessage.mileageInputMinLength, + ), + }, + required: { + value: true, + message: formatMessage( + vehicleMessage.mileageInputMinLength, + ), + }, + }} + /> + + ), + }, + { + value: ( + + ), + }, + ]} + > + {error ? ( + + ) : ( + [ + formatDate(r.date), + r.originCode, + '-', + displayWithUnit(r.mileage, 'km', true), + ], + ) ?? [] + } + /> + )} + + ) +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSaveButton.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSaveButton.tsx new file mode 100644 index 000000000000..f4184a1cd927 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageSaveButton.tsx @@ -0,0 +1,56 @@ +import { Button } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { m as coreMessages } from '@island.is/service-portal/core' +import { useMemo } from 'react' +import { type SubmissionState } from './types' + +interface Props { + submissionState: SubmissionState + onClick: () => void + disabled?: boolean +} + +export const VehicleBulkMileageSaveButton = ({ + submissionState, + onClick, + disabled, +}: Props) => { + const { formatMessage } = useLocale() + + const tag = useMemo(() => { + switch (submissionState) { + //case 'waiting-success': + case 'success': { + return { + text: formatMessage(coreMessages.saved), + icon: 'checkmarkCircle' as const, + } + } + //case 'waiting-failure': + case 'failure': { + return { + text: formatMessage(coreMessages.errorTitle), + icon: 'closeCircle' as const, + } + } + default: + return { + text: formatMessage(coreMessages.save), + icon: 'pencil' as const, + } + } + }, [formatMessage, submissionState]) + + return ( + + ) +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageTable.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageTable.tsx new file mode 100644 index 000000000000..3d0bb6e88e17 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/VehicleBulkMileageTable.tsx @@ -0,0 +1,88 @@ +import { Table as T, Box } from '@island.is/island-ui/core' +import { useLocale } from '@island.is/localization' +import { EmptyTable, ExpandHeader } from '@island.is/service-portal/core' +import { vehicleMessage } from '../../lib/messages' +import { useMemo } from 'react' +import { useFormContext } from 'react-hook-form' +import { SubmissionState, VehicleType } from './types' +import { VehicleBulkMileageRow } from './VehicleBulkMileageRow' + +interface Props { + vehicles: Array + loading: boolean + updateVehicleStatus: (status: SubmissionState, vehicleId: string) => void +} + +const VehicleBulkMileageTable = ({ + vehicles, + loading, + updateVehicleStatus, +}: Props) => { + const { formatMessage } = useLocale() + + const { getValues, trigger } = useFormContext() + + const getValueFromForm = async ( + formFieldId: string, + skipEmpty = false, + ): Promise => { + const value = getValues(formFieldId) + if (!value && skipEmpty) { + return + } + if (await trigger(formFieldId)) { + return value + } + return -1 + } + + const onRowPost = async (vehicleId: string) => { + const formValue = await getValueFromForm(vehicleId) + if (formValue && formValue > 0) { + //post stuff + updateVehicleStatus('success', vehicleId) + } else { + updateVehicleStatus('failure', vehicleId) + } + } + + const rows = useMemo(() => { + return vehicles.map((item) => ( + + )) + }, [formatMessage, vehicles]) + + return ( + +
+ {rows && ( + + + {rows} + + )} + {(!rows.length || loading) && ( + + )} + +
+ ) +} + +export default VehicleBulkMileageTable diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/mocks/propsDummy.ts b/libs/service-portal/assets/src/screens/VehicleBulkMileage/mocks/propsDummy.ts new file mode 100644 index 000000000000..f91ce032f6b7 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/mocks/propsDummy.ts @@ -0,0 +1,284 @@ +import { VehicleType } from '../types' + +export const dummy: Array = [ + { + vehicleId: 'AA001', + vehicleType: 'MAZDA 3', + submissionStatus: 'idle', + lastRegistrationDate: new Date('2021-06-11'), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 2, + }, + ], + }, + { + vehicleId: 'BCD89', + vehicleType: 'SUBARU IMPREZA', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'BZX09', + vehicleType: 'MAZDA 3', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'ED676', + vehicleType: 'SUBARU FORESTER', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'HMV76', + vehicleType: 'VOLKSWAGEN, VW CADDY', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'JZB99', + vehicleType: 'VOLVO V90', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'KDE08', + vehicleType: 'FORD FOCUS', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'PIU46', + vehicleType: 'FORD MUSTANG', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'ZX716', + vehicleType: 'MAN H29', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'ZAPPA', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'STAPPA', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'Parappa', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'zzzzzz', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'obboboj', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, + { + vehicleId: 'Habbebbi', + vehicleType: 'HONDA COOL', + submissionStatus: 'idle', + lastRegistrationDate: new Date(), + isCurrentlyEditing: false, + registrationHistory: [ + { + date: new Date(), + origin: 'ISLAND-IS', + mileage: 99, + }, + { + date: new Date('2021-06-11'), + origin: 'ISLAND-IS', + mileage: 3, + }, + ], + }, +] diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileage/types.ts b/libs/service-portal/assets/src/screens/VehicleBulkMileage/types.ts new file mode 100644 index 000000000000..236066dcb658 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileage/types.ts @@ -0,0 +1,34 @@ +export interface VehicleProps { + vehicleId: string + vehicleType: string + lastMileageRegistration?: Date + submissionStatus: SubmissionState +} + +export type SubmissionState = + | 'idle' + | 'success' + | 'failure' + | 'submit' + | 'submit-all' + | 'waiting-success' + | 'waiting-failure' + | 'waiting-idle' + +export interface Props { + vehicles: Array +} + +export interface VehicleType extends VehicleProps { + mileageUploadedFromFile?: number + isCurrentlyEditing?: boolean + registrationHistory?: Array<{ + date: Date + origin: string + mileage: number + }> +} + +export interface VehicleList { + vehicles: Array +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql new file mode 100644 index 000000000000..41047a57ba5c --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.graphql @@ -0,0 +1,24 @@ +query getJobsStatus($input: BulkVehicleMileageRequestStatusInput!) { + vehicleBulkMileageRegistrationRequestStatus(input: $input) { + jobsErrored + jobsFinished + jobsRemaining + jobsSubmitted + jobsValid + requestId + } +} +query getJobRegistrations($input: BulkVehicleMileageRequestOverviewInput!) { + vehicleBulkMileageRegistrationRequestOverview(input: $input) { + requests { + guid + vehicleId + mileage + returnCode + errors { + code + message + } + } + } +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx new file mode 100644 index 000000000000..d5a9a3316853 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobDetail/VehicleBulkMileageJobDetail.tsx @@ -0,0 +1,242 @@ +import { + Box, + Button, + Icon, + Stack, + Table as T, + Text, +} from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { + IntroHeader, + SAMGONGUSTOFA_SLUG, + m, + EmptyTable, + TableGrid, + downloadFile, +} from '@island.is/service-portal/core' +import { Problem } from '@island.is/react-spa/shared' +import { VehiclesBulkMileageRegistrationRequestStatus } from '@island.is/api/schema' +import { useParams } from 'react-router-dom' +import { + useGetJobRegistrationsQuery, + useGetJobsStatusQuery, +} from './VehicleBulkMileageJobDetail.generated' +import { VehiclesBulkMileageRegistrationRequestOverview } from '@island.is/service-portal/graphql' +import { displayWithUnit } from '../../utils/displayWithUnit' +import { useMemo } from 'react' +import { isDefined } from '@island.is/shared/utils' +import { vehicleMessage } from '../../lib/messages' + +type UseParams = { + id: string +} + +const VehicleBulkMileageJobDetail = () => { + useNamespaces('sp.vehicles') + const { formatMessage } = useLocale() + const { id } = useParams() as UseParams + + const { data, loading, error, refetch } = useGetJobsStatusQuery({ + variables: { + input: { + requestId: id, + }, + }, + }) + + const { + data: registrationData, + loading: registrationLoading, + error: registrationError, + } = useGetJobRegistrationsQuery({ + variables: { + input: { + guid: id, + }, + }, + }) + + const jobsStatus: VehiclesBulkMileageRegistrationRequestStatus | undefined = + data?.vehicleBulkMileageRegistrationRequestStatus ?? undefined + + const registrations: + | VehiclesBulkMileageRegistrationRequestOverview + | undefined = + registrationData?.vehicleBulkMileageRegistrationRequestOverview ?? undefined + + const tableArray = useMemo(() => { + if (data?.vehicleBulkMileageRegistrationRequestStatus) { + return [ + [ + { + title: formatMessage(vehicleMessage.totalSubmitted), + value: jobsStatus?.jobsSubmitted + ? jobsStatus.jobsSubmitted.toString() + : '0', + }, + { title: '', value: '' }, + ], + [ + { + title: formatMessage(vehicleMessage.totalFinished), + value: jobsStatus?.jobsFinished + ? jobsStatus.jobsFinished.toString() + : '0', + }, + { + title: formatMessage(vehicleMessage.totalRemaining), + value: jobsStatus?.jobsRemaining + ? jobsStatus.jobsRemaining.toString() + : '0', + }, + ], + [ + { + title: formatMessage(vehicleMessage.healthyJobs), + value: jobsStatus?.jobsValid + ? jobsStatus.jobsValid.toString() + : '0', + }, + { + title: formatMessage(vehicleMessage.unhealthyJobs), + value: jobsStatus?.jobsErrored + ? jobsStatus.jobsErrored.toString() + : '0', + }, + ], + ] + } + }, [data?.vehicleBulkMileageRegistrationRequestStatus]) + + const handleFileDownload = async () => { + const requests = registrations?.requests ?? [] + if (!requests.length) { + return + } + + const data: Array> = requests + .filter((r) => !!r.errors?.length) + .map((erroredVehicle) => { + if (!erroredVehicle.errors?.length) { + return null + } + return [ + erroredVehicle.vehicleId, + erroredVehicle.errors.map((j) => j.message).join(', '), + ] + }) + .filter(isDefined) + + downloadFile(`magnskraning_villur`, ['Ökutæki', 'Villur'], data, 'csv') + } + + return ( + + + {formatMessage(vehicleMessage.dataAboutJob)} +
+ {formatMessage(vehicleMessage.refreshDataAboutJob)} + + } + serviceProviderSlug={SAMGONGUSTOFA_SLUG} + serviceProviderTooltip={formatMessage(m.vehiclesTooltip)} + > + + + +
+ {!error && !loading && !jobsStatus && ( + + )} + {!error && ( + + + + {registrationError && } + + + + {formatMessage(vehicleMessage.jobsSubmitted)} + + + + + + + + {formatMessage(vehicleMessage.permno)} + + + {formatMessage(vehicleMessage.odometer)} + + + {formatMessage(vehicleMessage.errors)} + + + + + + {!!registrations?.requests.length && + registrations?.requests.map((j) => ( + + + + + {j.vehicleId} + + + {displayWithUnit(j.mileage, 'km', true)} + + {(j.errors ?? []).map((j) => j.message).join(', ')} + + + ))} + + + {(!registrations || registrationLoading) && ( + + )} + + + )} +
+ ) +} + +export default VehicleBulkMileageJobDetail diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.graphql b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.graphql new file mode 100644 index 000000000000..493fc8d24804 --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.graphql @@ -0,0 +1,14 @@ +query getRequestsStatus { + vehicleBulkMileageRegistrationJobHistory { + history { + guid + reportingPersonNationalId + reportingPersonName + originCode + originName + dateRequested + dateStarted + dateFinished + } + } +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx new file mode 100644 index 000000000000..648ef2116b7c --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageJobOverview/VehicleBulkMileageJobOverview.tsx @@ -0,0 +1,148 @@ +import { Box, Table as T, Tag } from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { + IntroHeader, + SAMGONGUSTOFA_SLUG, + m, + LinkButton, + EmptyTable, + formatDateWithTime, +} from '@island.is/service-portal/core' +import { Problem } from '@island.is/react-spa/shared' +import { useGetRequestsStatusQuery } from './VehicleBulkMileageJobOverview.generated' +import { VehiclesBulkMileageRegistrationJob } from '@island.is/api/schema' +import { AssetsPaths } from '../../lib/paths' +import { vehicleMessage } from '../../lib/messages' +import compareDesc from 'date-fns/compareDesc' + +const sortDate = (dateOne?: Date, dateTwo?: Date) => { + if (!dateOne && !dateTwo) { + return 0 + } else if (!dateOne) { + return -1 + } else if (!dateTwo) { + return 1 + } + const l = compareDesc(dateOne, dateTwo) + return l +} + +const sortJobs = ( + jobOne: VehiclesBulkMileageRegistrationJob, + jobTwo: VehiclesBulkMileageRegistrationJob, +) => { + let sortedValue = 0 + sortedValue = sortDate( + jobOne.dateFinished ? new Date(jobOne.dateFinished) : undefined, + jobTwo.dateFinished ? new Date(jobTwo.dateFinished) : undefined, + ) + + if (sortedValue === 0) { + sortedValue = sortDate( + jobOne.dateStarted ? new Date(jobOne.dateStarted) : undefined, + jobTwo.dateStarted ? new Date(jobTwo.dateStarted) : undefined, + ) + } + + if (sortedValue === 0) { + sortedValue = sortDate( + jobOne.dateRequested ? new Date(jobOne.dateRequested) : undefined, + jobTwo.dateRequested ? new Date(jobTwo.dateRequested) : undefined, + ) + } + + return sortedValue +} + +const VehicleBulkMileageUploadJobOverview = () => { + useNamespaces('sp.vehicles') + const { formatMessage } = useLocale() + + const { data, loading, error } = useGetRequestsStatusQuery() + + const jobs: Array = + data?.vehicleBulkMileageRegistrationJobHistory?.history ?? [] + + const sortedJobs = jobs.length > 1 ? [...jobs] : [] + sortedJobs.sort((a, b) => sortJobs(a, b)) + return ( + + + {error && } + {!error && ( + + + + + {formatMessage(vehicleMessage.jobSubmitted)} + + + {formatMessage(vehicleMessage.jobStarted)} + + + {formatMessage(vehicleMessage.jobFinished)} + + + + + + {sortedJobs.map((j) => ( + + + {j.dateRequested ? formatDateWithTime(j.dateRequested) : '-'} + + + {j.dateStarted ? ( + formatDateWithTime(j.dateStarted) + ) : j.dateRequested ? ( + + {formatMessage(vehicleMessage.jobNotStarted)} + + ) : ( + '' + )} + + + {j.dateFinished ? ( + formatDateWithTime(j.dateFinished) + ) : j.dateStarted ? ( + + {formatMessage(vehicleMessage.jobInProgress)} + + ) : ( + '' + )} + + + + + + + ))} + + + )} + {!error && (loading || !jobs.length) && ( + + )} + + ) +} + +export default VehicleBulkMileageUploadJobOverview diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.graphql b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.graphql new file mode 100644 index 000000000000..5a1a216ada0a --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.graphql @@ -0,0 +1,6 @@ +mutation vehicleBulkMileagePost($input: PostVehicleBulkMileageInput!) { + vehicleBulkMileagePost(input: $input) { + requestId + errorMessage + } +} diff --git a/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx new file mode 100644 index 000000000000..886234b4e80f --- /dev/null +++ b/libs/service-portal/assets/src/screens/VehicleBulkMileageUpload/VehicleBulkMileageUpload.tsx @@ -0,0 +1,174 @@ +import { + InputFileUpload, + Box, + Text, + fileToObject, + AlertMessage, + Stack, +} from '@island.is/island-ui/core' +import { useLocale, useNamespaces } from '@island.is/localization' +import { useEffect, useState } from 'react' +import { FileRejection } from 'react-dropzone' +import { + IntroHeader, + LinkButton, + SAMGONGUSTOFA_SLUG, + m, +} from '@island.is/service-portal/core' +import { + MileageRecord, + parseCsvToMileageRecord, +} from '../../utils/parseCsvToMileage' +import { Problem } from '@island.is/react-spa/shared' +import { AssetsPaths } from '../../lib/paths' +import { useVehicleBulkMileagePostMutation } from './VehicleBulkMileageUpload.generated' +import VehicleBulkMileageFileDownloader from '../VehicleBulkMileage/VehicleBulkMileageFileDownloader' +import { vehicleMessage } from '../../lib/messages' + +const VehicleBulkMileageUpload = () => { + useNamespaces('sp.vehicles') + const { formatMessage } = useLocale() + + const [vehicleBulkMileagePostMutation, { data, loading, error }] = + useVehicleBulkMileagePostMutation() + + const [uploadedFile, setUploadedFile] = useState() + const [uploadErrorMessage, setUploadErrorMessage] = useState( + null, + ) + const [downloadError, setDownloadError] = useState() + + const [requestGuid, setRequestGuid] = useState() + + useEffect(() => { + const id = data?.vehicleBulkMileagePost?.requestId + if (id && id !== requestGuid) { + setRequestGuid(id) + } + }, [data?.vehicleBulkMileagePost?.requestId]) + + const postMileage = async (file: File) => { + try { + const records = await parseCsvToMileageRecord(file) + if (!records.length) { + setUploadErrorMessage(formatMessage(vehicleMessage.uploadFailed)) + return + } + vehicleBulkMileagePostMutation({ + variables: { + input: { + mileageData: records.map((r) => ({ + mileageNumber: r.mileage, + vehicleId: r.vehicleId, + })), + originCode: 'ISLAND.IS', + }, + }, + }) + } catch (error) { + setUploadErrorMessage( + `${formatMessage(vehicleMessage.errorWhileProcessing) + error.message} + `, + ) + } + } + + const handleOnInputFileUploadError = (files: FileRejection[]) => + setUploadErrorMessage(files[0].errors[0].message) + + const handleOnInputFileUploadRemove = () => setUploadedFile(null) + + const handleOnInputFileUploadChange = (files: File[]) => { + const file = fileToObject(files[0]) + if (file.status === 'done' && file.originalFileObj instanceof File) { + postMileage(file.originalFileObj) + } + } + + const handleFileDownloadError = (error: string) => { + setDownloadError(error) + } + + return ( + + + + + + + + + {error && } + {data?.vehicleBulkMileagePost?.errorMessage && !loading && !error && ( + + )} + {downloadError && ( + + )} + {data?.vehicleBulkMileagePost?.errorMessage && !loading && !error && ( + + )} + {data?.vehicleBulkMileagePost?.requestId && + !data?.vehicleBulkMileagePost?.errorMessage && + !loading && + !error && ( + + + {formatMessage(vehicleMessage.bulkMileageUploadStatus)} + + + + + + } + /> + )} + + + + ) +} + +export default VehicleBulkMileageUpload diff --git a/libs/service-portal/assets/src/utils/makeArrayEven.ts b/libs/service-portal/assets/src/utils/makeArrayEven.ts index d17f09aa0fca..f2d5549be999 100644 --- a/libs/service-portal/assets/src/utils/makeArrayEven.ts +++ b/libs/service-portal/assets/src/utils/makeArrayEven.ts @@ -1,4 +1,4 @@ -export function makeArrayEven(data: Array, toAddIfOdd: T) { +export const makeArrayEven = (data: Array, toAddIfOdd: T) => { if (data.length % 2 !== 0) { data.push(toAddIfOdd) } diff --git a/libs/service-portal/assets/src/utils/parseCsvToMileage.ts b/libs/service-portal/assets/src/utils/parseCsvToMileage.ts new file mode 100644 index 000000000000..8ab44d003c23 --- /dev/null +++ b/libs/service-portal/assets/src/utils/parseCsvToMileage.ts @@ -0,0 +1,58 @@ +import { isDefined } from '@island.is/shared/utils' + +export interface MileageRecord { + vehicleId: string + mileage: number +} + +const letters = + 'aábcdðeéfghiíjklmnoópqrstuúvwxyýzþæöAÁBCDÐEÉFGHIÍJKLMNOÓPQRSTUÚVWXYÝZÞÆÖ' +const newlines = '\\Q\\r\\n\\E|\\r|\\n' +const wordbreaks = '[;,]' + +export const parseCsvToMileageRecord = async (file: File) => { + const reader = file.stream().getReader() + + const parsedLines: Array> = [[]] + const parseChunk = async (res: ReadableStreamReadResult) => { + if (res.done) { + return + } + const chunk = Buffer.from(res.value).toString('utf8') + + let rowIndex = 0 + for (const cell of chunk.matchAll( + new RegExp(`([${letters}\\d]+)(${newlines}|${wordbreaks})?`, 'gi'), + )) { + const [_, trimmedValue, delimiter] = cell + const lineBreak = ['\r\n', '\n', '\r'].includes(delimiter) + + parsedLines[rowIndex].push(trimmedValue.trim()) + if (lineBreak) { + parsedLines.push([]) + rowIndex++ + } + } + } + + await reader.read().then(parseChunk) + const [header, ...values] = parsedLines + const vehicleIndex = header.findIndex((l) => l.toLowerCase() === 'ökutæki') + const mileageIndex = header.findIndex( + (l) => l.toLowerCase() === 'kílómetraskráning', + ) + const uploadedOdometerStatuses: Array = values + .map((row) => { + const mileage = parseInt(row[mileageIndex]) + if (Number.isNaN(mileage)) { + return undefined + } + return { + vehicleId: row[vehicleIndex], + mileage, + } + }) + .filter(isDefined) + + return uploadedOdometerStatuses +} diff --git a/libs/service-portal/assets/src/utils/vehicleOwnedMapper.ts b/libs/service-portal/assets/src/utils/vehicleOwnedMapper.ts index 9b91a8325efd..2c87bb23eed2 100644 --- a/libs/service-portal/assets/src/utils/vehicleOwnedMapper.ts +++ b/libs/service-portal/assets/src/utils/vehicleOwnedMapper.ts @@ -5,11 +5,7 @@ import { } from './dataHeaders' import isValid from 'date-fns/isValid' import { LOCALE } from './constants' -import { - Query, - VehicleUserTypeEnum, - VehiclesDetail, -} from '@island.is/api/schema' +import { VehicleUserTypeEnum, VehiclesDetail } from '@island.is/api/schema' export const exportVehicleOwnedDocument = async ( data: Array, @@ -30,12 +26,12 @@ export const exportVehicleOwnedDocument = async ( ) } -function filterOwners( +const filterOwners = ( role: VehicleUserTypeEnum, vehicles: Array, nationalId: string, name: string, -) { +) => { let filteredVehicles switch (role) { diff --git a/libs/service-portal/core/src/components/LinkButton/LinkButton.tsx b/libs/service-portal/core/src/components/LinkButton/LinkButton.tsx index edf0aa6ffa82..229ecf58217a 100644 --- a/libs/service-portal/core/src/components/LinkButton/LinkButton.tsx +++ b/libs/service-portal/core/src/components/LinkButton/LinkButton.tsx @@ -11,18 +11,10 @@ interface SharedProps { skipOutboundTrack?: boolean } -type Props = - | { - variant?: 'primary' | 'ghost' | 'utility' - icon?: ButtonProps['icon'] - } - | { - /** - * default variant is "text" - */ - variant?: 'text' - icon?: never - } +type Props = { + variant?: 'primary' | 'ghost' | 'utility' | 'text' + icon?: ButtonProps['icon'] +} type LinkButtonProps = SharedProps & Props diff --git a/libs/service-portal/core/src/components/NestedTable/NestedFullTable.tsx b/libs/service-portal/core/src/components/NestedTable/NestedFullTable.tsx new file mode 100644 index 000000000000..4d61100c55de --- /dev/null +++ b/libs/service-portal/core/src/components/NestedTable/NestedFullTable.tsx @@ -0,0 +1,76 @@ +import { Box, Table as T, Text } from '@island.is/island-ui/core' +import { tableStyles } from '../../utils/utils' + +import * as styles from './NestedTable.css' +import { EmptyTable } from '../EmptyTable/EmptyTable' + +interface Props { + headerArray: string[] + data: Array + loading?: boolean + emptyMessage?: string +} + +export const NestedFullTable = ({ + headerArray, + data, + loading, + emptyMessage, +}: Props) => { + return ( + + {!loading && data.length && ( + + + + {headerArray.map((item, i) => ( + 1 ? 'right' : 'left', + paddingRight: 2, + paddingLeft: 2, + className: styles.noBorder, + }} + key={i} + text={{ truncate: true }} + style={tableStyles} + > + + {item} + + + ))} + + + + {data?.map((row, i) => ( + + {row.map((value, ii) => ( + 1 ? 'right' : 'left', + background: i % 2 === 0 ? 'white' : undefined, + className: styles.noBorder, + }} + key={ii} + style={tableStyles} + > + {value} + + ))} + + ))} + + + )} + {(loading || !data.length) && ( + + )} + + ) +} diff --git a/libs/service-portal/core/src/components/NestedTable/NestedTable.css.ts b/libs/service-portal/core/src/components/NestedTable/NestedTable.css.ts index a730679cba25..c74de3a35e44 100644 --- a/libs/service-portal/core/src/components/NestedTable/NestedTable.css.ts +++ b/libs/service-portal/core/src/components/NestedTable/NestedTable.css.ts @@ -16,3 +16,21 @@ export const white = style({ export const titleCol = style({ paddingLeft: theme.spacing[2], }) + +export const wrapper = style({ + display: 'grid', + background: theme.color.blue100, + padding: `0 ${theme.spacing[3]}px ${theme.spacing[3]}px ${theme.spacing[3]}px`, +}) + +export const td = style({ + width: 'max-content', +}) + +export const alignTd = style({ + marginLeft: 'auto', +}) + +export const noBorder = style({ + border: 'none', +}) diff --git a/libs/service-portal/core/src/index.ts b/libs/service-portal/core/src/index.ts index 9f528d7f1c1f..25c5a74f64a0 100644 --- a/libs/service-portal/core/src/index.ts +++ b/libs/service-portal/core/src/index.ts @@ -38,6 +38,7 @@ export * from './components/AudioPlayer/AudioPlayer' export * from './components/VideoPlayer/VideoPlayer' export * from './components/DownloadFileButtons/DownloadFileButtons' export * from './components/NestedTable/NestedTable' +export * from './components/NestedTable/NestedFullTable' export * from './components/EmptyTable/EmptyTable' export * from './components/StackWithBottomDivider/StackWithBottomDivider' export * from './components/StackOrTableBlock/StackOrTableBlock' diff --git a/libs/service-portal/core/src/lib/messages.ts b/libs/service-portal/core/src/lib/messages.ts index 3a5bff6b5f84..e522a617fe8a 100644 --- a/libs/service-portal/core/src/lib/messages.ts +++ b/libs/service-portal/core/src/lib/messages.ts @@ -305,6 +305,35 @@ export const m = defineMessages({ id: 'service.portal:vehicles-lookup', defaultMessage: 'Uppfletting í ökutækjaskrá', }, + vehiclesBulkMileage: { + id: 'service.portal:vehicles-bulk-mileage', + defaultMessage: 'Magnskráning kílómetrastöðu', + }, + vehiclesBulkMileageUpload: { + id: 'service.portal:vehicles-bulk-mileage-upload', + defaultMessage: 'Magnskrá með skjali', + }, + vehiclesBulkMileageUploadDescription: { + id: 'service.portal:vehicles-bulk-mileage-upload-description', + defaultMessage: + 'Hér geturu hlaðið upp skjali til að magnskrá kílómetrastöður', + }, + vehiclesBulkMileageJobOverview: { + id: 'service.portal:vehicles-bulk-mileage-job-overview', + defaultMessage: 'Yfirlit skráninga', + }, + vehiclesBulkMileageJobOverviewDescription: { + id: 'service.portal:vehicles-bulk-mileage-job-overview', + defaultMessage: 'Yfirlit yfir skráða kílómetrastöðu', + }, + vehiclesBulkMileageJobDetail: { + id: 'service.portal:vehicles-bulk-mileage-job-detail', + defaultMessage: 'Magnskráning', + }, + vehiclesBulkMileageJobRegistration: { + id: 'service.portal:vehicles-bulk-mileage-job-registration', + defaultMessage: 'Hvaða skráningar eru höndlaðar í þessu verki', + }, vehiclesDrivingLessons: { id: 'service.portal:vehicles-driving-lessons', defaultMessage: 'Ökunám', @@ -1084,6 +1113,10 @@ export const m = defineMessages({ id: 'service.portal:save', defaultMessage: 'Vista', }, + saved: { + id: 'service.portal:saved', + defaultMessage: 'Vistað', + }, register: { id: 'service.portal:register', defaultMessage: 'Skrá', From d399cd8e35c278b53e67d5774950432fdd9f6285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A6var=20M=C3=A1r=20Atlason?= <54210288+saevarma@users.noreply.github.com> Date: Sun, 29 Sep 2024 21:40:00 +0000 Subject: [PATCH 18/18] fix(auth-api): Add audit logs to index with alsoLog set to true for server logs (#16203) * Adding audit log to delegationIndex saveToIndex method to track changes to users delegations records in the index. * Add audit to create and remove in index service. --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../delegation-index.controller.ts | 24 +++-- .../delegation-index.service.spec.ts | 9 +- .../personalRepresentatives.controller.ts | 3 + .../admin/delegation-admin-custom.service.ts | 15 +-- .../delegations/delegation-scope.service.ts | 25 ++--- .../delegations-incoming.service.ts | 15 +-- .../delegations/delegations-index.service.ts | 91 ++++++++++++++----- .../delegations-outgoing.service.ts | 6 +- 8 files changed, 130 insertions(+), 58 deletions(-) diff --git a/apps/services/auth/delegation-api/src/app/delegations/delegation-index.controller.ts b/apps/services/auth/delegation-api/src/app/delegations/delegation-index.controller.ts index 3c37004049dc..6929de34458c 100644 --- a/apps/services/auth/delegation-api/src/app/delegations/delegation-index.controller.ts +++ b/apps/services/auth/delegation-api/src/app/delegations/delegation-index.controller.ts @@ -93,11 +93,14 @@ export class DelegationIndexController { ...parsedDelegationInfo, }, }, - this.delegationIndexService.createOrUpdateDelegationRecord({ - ...parsedDelegationInfo, - provider: auth.delegationProvider, - validTo: body.validTo, - }), + this.delegationIndexService.createOrUpdateDelegationRecord( + { + ...parsedDelegationInfo, + provider: auth.delegationProvider, + validTo: body.validTo, + }, + auth, + ), ) } @@ -126,10 +129,13 @@ export class DelegationIndexController { ...parsedDelegationInfo, }, }, - this.delegationIndexService.removeDelegationRecord({ - ...parsedDelegationInfo, - provider: auth.delegationProvider, - }), + this.delegationIndexService.removeDelegationRecord( + { + ...parsedDelegationInfo, + provider: auth.delegationProvider, + }, + auth, + ), ) } } diff --git a/apps/services/auth/delegation-api/src/app/delegations/test/delegation-index/delegation-index.service.spec.ts b/apps/services/auth/delegation-api/src/app/delegations/test/delegation-index/delegation-index.service.spec.ts index bd627e8b37be..d265b3d64fb3 100644 --- a/apps/services/auth/delegation-api/src/app/delegations/test/delegation-index/delegation-index.service.spec.ts +++ b/apps/services/auth/delegation-api/src/app/delegations/test/delegation-index/delegation-index.service.spec.ts @@ -451,7 +451,7 @@ describe('DelegationsIndexService', () => { const nationalId = user.nationalId // Act - await delegationIndexService.indexCustomDelegations(nationalId) + await delegationIndexService.indexCustomDelegations(nationalId, user) // Assert const delegations = await delegationIndexModel.findAll({ @@ -480,7 +480,10 @@ describe('DelegationsIndexService', () => { const nationalId = user.nationalId // Act - await delegationIndexService.indexRepresentativeDelegations(nationalId) + await delegationIndexService.indexRepresentativeDelegations( + nationalId, + user, + ) // Assert const delegations = await delegationIndexModel.findAll({ @@ -625,7 +628,7 @@ describe('DelegationsIndexService', () => { const nationalId = user.nationalId // Act - await delegationIndexService.indexCustomDelegations(nationalId) + await delegationIndexService.indexCustomDelegations(nationalId, user) // Assert const delegations = await delegationModel.findAll({ diff --git a/apps/services/auth/personal-representative/src/app/modules/personalRepresentatives/personalRepresentatives.controller.ts b/apps/services/auth/personal-representative/src/app/modules/personalRepresentatives/personalRepresentatives.controller.ts index 8e7f9a477f4c..00af813def2c 100644 --- a/apps/services/auth/personal-representative/src/app/modules/personalRepresentatives/personalRepresentatives.controller.ts +++ b/apps/services/auth/personal-representative/src/app/modules/personalRepresentatives/personalRepresentatives.controller.ts @@ -99,6 +99,7 @@ export class PersonalRepresentativesController { return personalRepresentative } + /** Removes a personal representative by its id */ @Delete(':id') @Documentation({ @@ -135,6 +136,7 @@ export class PersonalRepresentativesController { // Index personal representative delegations for the personal representative void this.delegationIndexService.indexRepresentativeDelegations( deletedPersonalRepresentative.nationalIdPersonalRepresentative, + user, ) } } @@ -203,6 +205,7 @@ export class PersonalRepresentativesController { // Index personal representative delegations for the personal representative void this.delegationIndexService.indexRepresentativeDelegations( createdPersonalRepresentative.nationalIdPersonalRepresentative, + user, ) } diff --git a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts index 43ced573b3d8..04403fecda27 100644 --- a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts @@ -5,7 +5,7 @@ import kennitala from 'kennitala' import { uuid } from 'uuidv4' import { AuthDelegationType } from '@island.is/shared/types' -import { User } from '@island.is/auth-nest-tools' +import { Auth, User } from '@island.is/auth-nest-tools' import { NoContentException } from '@island.is/nest/problem' import { Ticket, @@ -201,7 +201,7 @@ export class DelegationAdminCustomService { ) // Index delegations for the toNationalId - void this.indexDelegations(delegation.toNationalId) + void this.indexDelegations(delegation.toNationalId, user) return newDelegation.toDTO(AuthDelegationType.GeneralMandate) } @@ -244,7 +244,7 @@ export class DelegationAdminCustomService { } // Index delegations for the toNationalId - void this.indexDelegations(delegation.toNationalId) + void this.indexDelegations(delegation.toNationalId, user) }) } @@ -267,8 +267,11 @@ export class DelegationAdminCustomService { } } - private indexDelegations(nationalId: string) { - void this.delegationIndexService.indexCustomDelegations(nationalId) - void this.delegationIndexService.indexGeneralMandateDelegations(nationalId) + private indexDelegations(nationalId: string, auth: Auth) { + void this.delegationIndexService.indexCustomDelegations(nationalId, auth) + void this.delegationIndexService.indexGeneralMandateDelegations( + nationalId, + auth, + ) } } diff --git a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts index 94e89cc29204..d12b1d8af8e4 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegation-scope.service.ts @@ -28,6 +28,7 @@ import { DelegationTypeModel } from './models/delegation-type.model' import { Delegation } from './models/delegation.model' import type { User } from '@island.is/auth-nest-tools' + @Injectable() export class DelegationScopeService { constructor( @@ -309,24 +310,27 @@ export class DelegationScopeService { } private async findDistrictCommissionersRegistryScopesTo( - toNationalId: string, + user: User, fromNationalId: string, ): Promise { // if no valid delegation exists, return empty array try { const delegationFound = await this.syslumennService.checkIfDelegationExists( - toNationalId, + user.nationalId, fromNationalId, ) if (!delegationFound) { - this.delegationsIndexService.removeDelegationRecord({ - fromNationalId, - toNationalId, - type: AuthDelegationType.LegalRepresentative, - provider: AuthDelegationProvider.DistrictCommissionersRegistry, - }) + void this.delegationsIndexService.removeDelegationRecord( + { + fromNationalId, + toNationalId: user.nationalId, + type: AuthDelegationType.LegalRepresentative, + provider: AuthDelegationProvider.DistrictCommissionersRegistry, + }, + user, + ) return [] } } catch (error) { @@ -429,10 +433,7 @@ export class DelegationScopeService { providers.includes(AuthDelegationProvider.DistrictCommissionersRegistry) ) scopePromises.push( - this.findDistrictCommissionersRegistryScopesTo( - user.nationalId, - fromNationalId, - ), + this.findDistrictCommissionersRegistryScopesTo(user, fromNationalId), ) const scopeSets = await Promise.all(scopePromises) diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts index ca782439de6c..1792164ddb48 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-incoming.service.ts @@ -302,12 +302,15 @@ export class DelegationsIncomingService { if (delegationFound) { return true } else { - this.delegationsIndexService.removeDelegationRecord({ - fromNationalId, - toNationalId: user.nationalId, - type: AuthDelegationType.LegalRepresentative, - provider: AuthDelegationProvider.DistrictCommissionersRegistry, - }) + void this.delegationsIndexService.removeDelegationRecord( + { + fromNationalId, + toNationalId: user.nationalId, + type: AuthDelegationType.LegalRepresentative, + provider: AuthDelegationProvider.DistrictCommissionersRegistry, + }, + user, + ) } } catch (error) { logger.error( diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts index 46b51cd42f48..de23fd31904d 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-index.service.ts @@ -1,11 +1,13 @@ -import { BadRequestException, Injectable } from '@nestjs/common' +import { BadRequestException, Inject, Injectable } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' import startOfDay from 'date-fns/startOfDay' import * as kennitala from 'kennitala' import union from 'lodash/union' import { Op } from 'sequelize' -import { User } from '@island.is/auth-nest-tools' +import { Auth, User } from '@island.is/auth-nest-tools' +import { AuditService } from '@island.is/nest/audit' +import { type Logger, LOGGER_PROVIDER } from '@island.is/logging' import { AuthDelegationProvider, AuthDelegationType, @@ -170,6 +172,9 @@ export class DelegationsIndexService { private delegationsIncomingWardService: DelegationsIncomingWardService, private personalRepresentativeScopePermissionService: PersonalRepresentativeScopePermissionService, private userIdentitiesService: UserIdentitiesService, + @Inject(LOGGER_PROVIDER) + private logger: Logger, + private auditService: AuditService, ) {} /* Lookup delegations in index for user for specific scope */ @@ -258,7 +263,7 @@ export class DelegationsIndexService { this.getWardDelegations(user), ]).then((d) => d.flat()) - await this.saveToIndex(user.nationalId, delegations) + await this.saveToIndex(user.nationalId, delegations, user) // set next reindex to one week in the future await this.delegationIndexMetaModel.update( @@ -275,49 +280,76 @@ export class DelegationsIndexService { } /* Index incoming custom delegations */ - async indexCustomDelegations(nationalId: string) { + async indexCustomDelegations(nationalId: string, auth: Auth) { const delegations = await this.getCustomDelegations(nationalId, true) - await this.saveToIndex(nationalId, delegations) + await this.saveToIndex(nationalId, delegations, auth) } /* Index incoming general mandate delegations */ - async indexGeneralMandateDelegations(nationalId: string) { + async indexGeneralMandateDelegations(nationalId: string, auth: Auth) { const delegations = await this.getGeneralMandateDelegation(nationalId, true) - await this.saveToIndex(nationalId, delegations) + await this.saveToIndex(nationalId, delegations, auth) } /* Index incoming personal representative delegations */ - async indexRepresentativeDelegations(nationalId: string) { + async indexRepresentativeDelegations(nationalId: string, auth: Auth) { const delegations = await this.getRepresentativeDelegations( nationalId, true, ) - await this.saveToIndex(nationalId, delegations) + await this.saveToIndex(nationalId, delegations, auth) } /* Add item to index */ - async createOrUpdateDelegationRecord(delegation: DelegationRecordInputDTO) { + async createOrUpdateDelegationRecord( + delegation: DelegationRecordInputDTO, + auth: Auth, + ) { validateCrudParams(delegation) - const [updatedDelegation] = await this.delegationIndexModel.upsert( - delegation, + const [updatedDelegation] = await this.auditService.auditPromise( + { + auth, + action: + '@island.is/auth/delegation-index/create-or-update-delegation-record', + resources: delegation.toNationalId, + alsoLog: true, + meta: { + delegation, + }, + }, + this.delegationIndexModel.upsert(delegation), ) return updatedDelegation.toDTO() } /* Delete record from index */ - async removeDelegationRecord(delegation: DelegationRecordInputDTO) { + async removeDelegationRecord( + delegation: DelegationRecordInputDTO, + auth: Auth, + ) { validateCrudParams(delegation) - await this.delegationIndexModel.destroy({ - where: { - fromNationalId: delegation.fromNationalId, - toNationalId: delegation.toNationalId, - provider: delegation.provider, - type: delegation.type, + await this.auditService.auditPromise( + { + auth, + action: '@island.is/auth/delegation-index/remove-delegation-record', + resources: delegation.toNationalId, + alsoLog: true, + meta: { + delegation, + }, }, - }) + this.delegationIndexModel.destroy({ + where: { + fromNationalId: delegation.fromNationalId, + toNationalId: delegation.toNationalId, + provider: delegation.provider, + type: delegation.type, + }, + }), + ) } async getAvailableDistrictCommissionersRegistryRecords( @@ -338,7 +370,7 @@ export class DelegationsIndexService { } } - return await this.delegationIndexModel + return this.delegationIndexModel .findAll({ where: { toNationalId: user.nationalId, @@ -356,6 +388,9 @@ export class DelegationsIndexService { private async saveToIndex( nationalId: string, delegations: DelegationIndexInfo[], + // Some entrypoints to indexing do not have a user auth object or have a 3rd party user + // so we take the auth separately from the subject nationalId + auth: Auth, ) { const currRecords = await this.delegationIndexModel.findAll({ where: { @@ -398,6 +433,20 @@ export class DelegationsIndexService { }), ), ]) + + // saveToIndex is used by multiple entry points, when indexing so this + // is the common place to audit updates in the index. + this.auditService.audit({ + auth, + action: '@island.is/auth/delegation-index/save-to-index', + alsoLog: true, + resources: nationalId, + meta: { + created, + updated, + deleted, + }, + }) } private sortDelegation({ diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts index 807298a7fa2a..3252f24c3e31 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts @@ -273,6 +273,7 @@ export class DelegationsOutgoingService { // Index custom delegations for the toNationalId void this.delegationIndexService.indexCustomDelegations( createDelegation.toNationalId, + user, ) return newDelegation @@ -418,11 +419,13 @@ export class DelegationsOutgoingService { // Index custom delegations for the toNationalId void this.delegationIndexService.indexCustomDelegations( delegation.toNationalId, + user, ) const hasExistingScopes = (currentDelegation.delegationScopes?.length ?? 0) > 0 - this.notifyDelegationUpdate(user, delegation, hasExistingScopes) + + void this.notifyDelegationUpdate(user, delegation, hasExistingScopes) return delegation } @@ -457,6 +460,7 @@ export class DelegationsOutgoingService { // Index custom delegations for the toNationalId void this.delegationIndexService.indexCustomDelegations( delegation.toNationalId, + user, ) }