diff --git a/apps/application-system/api/src/app/app.module.ts b/apps/application-system/api/src/app/app.module.ts index 4d8550a066d5..ab7d68cc01c0 100644 --- a/apps/application-system/api/src/app/app.module.ts +++ b/apps/application-system/api/src/app/app.module.ts @@ -55,6 +55,7 @@ import { DataProtectionComplaintClientConfig } from '@island.is/clients/data-pro import { CriminalRecordClientConfig } from '@island.is/clients/criminal-record' import { HealthInsuranceV2ClientConfig } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { VmstClientConfig } from '@island.is/clients/vmst' +import { RightsPortalClientConfig } from '@island.is/clients/icelandic-health-insurance/rights-portal' import { FriggClientConfig } from '@island.is/clients/mms/frigg' import { smsModuleConfig } from '@island.is/nova-sms' import { emailModuleConfig } from '@island.is/email-service' @@ -112,6 +113,7 @@ import { emailModuleConfig } from '@island.is/email-service' CriminalRecordClientConfig, HealthInsuranceV2ClientConfig, VmstClientConfig, + RightsPortalClientConfig, FriggClientConfig, smsModuleConfig, emailModuleConfig, diff --git a/libs/api/domains/application/src/lib/application.service.ts b/libs/api/domains/application/src/lib/application.service.ts index fbfbab4cdc08..4fb572897be5 100644 --- a/libs/api/domains/application/src/lib/application.service.ts +++ b/libs/api/domains/application/src/lib/application.service.ts @@ -115,6 +115,7 @@ export class ApplicationService { to: input.to, }) } + async create(input: CreateApplicationInput, auth: Auth) { return this.applicationApiWithAuth(auth).applicationControllerCreate({ createApplicationDto: input, diff --git a/libs/api/domains/health-insurance/src/lib/accident-notification.service.ts b/libs/api/domains/health-insurance/src/lib/accident-notification.service.ts index d711490893d9..bf112489a2cc 100644 --- a/libs/api/domains/health-insurance/src/lib/accident-notification.service.ts +++ b/libs/api/domains/health-insurance/src/lib/accident-notification.service.ts @@ -1,7 +1,6 @@ import { Inject, Injectable } from '@nestjs/common' import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' -import { DocumentApi } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { HealthInsuranceAccidentNotificationAttachmentTypes as AttachmentTypes, HealthInsuranceAccidentNotificationConfirmationTypes as ConfirmationTypes, @@ -12,6 +11,8 @@ import { AccidentNotificationConfirmation, AccidentNotificationStatus, } from './graphql/models' +import { AccidentreportsApi } from '@island.is/clients/icelandic-health-insurance/rights-portal' +import { Auth, AuthMiddleware } from '@island.is/auth-nest-tools' const mapStatus = (statusId: number) => { switch (statusId) { @@ -55,26 +56,34 @@ const mapConfirmationType = (confirmationTypeId: number | undefined) => { @Injectable() export class AccidentNotificationService { constructor( - private readonly accidentNotificationApi: DocumentApi, + private readonly accidentReportsApi: AccidentreportsApi, @Inject(LOGGER_PROVIDER) private logger: Logger, ) {} + private accidentsReportsApiWithAuth(auth: Auth) { + return this.accidentReportsApi.withMiddleware(new AuthMiddleware(auth)) + } + async getAccidentNotificationStatus( + auth: Auth, ihiDocumentID: number, ): Promise { this.logger.log('starting call to get accident', ihiDocumentID) - const accidentStatus = - await this.accidentNotificationApi.documentGetAccidentStatus({ - ihiDocumentID: ihiDocumentID, - }) + + const accidentStatus = await this.accidentsReportsApiWithAuth( + auth, + ).getAccidentReportStatus({ + reportId: ihiDocumentID, + }) + if (!accidentStatus) return null return { - numberIHI: accidentStatus.numberIHI, + numberIHI: accidentStatus.requestId, status: accidentStatus.status ? mapStatus(accidentStatus.status) : '', receivedAttachments: accidentStatus.attachments ?.map((x) => ({ - [mapAttachmentType(x.attachmentType)]: !!x.isReceived, + [mapAttachmentType(x.type)]: !!x.received, })) .reduce( (prev, curr) => ({ ...prev, ...curr }), @@ -82,7 +91,7 @@ export class AccidentNotificationService { ) as AccidentNotificationAttachment, receivedConfirmations: accidentStatus.confirmations ?.map((x) => ({ - [mapConfirmationType(x.confirmationType)]: !!x.isReceived, + [mapConfirmationType(x.party)]: !!x.received, })) .reduce( (prev, curr) => ({ ...prev, ...curr }), diff --git a/libs/api/domains/health-insurance/src/lib/graphql/accident-notification.resolver.ts b/libs/api/domains/health-insurance/src/lib/graphql/accident-notification.resolver.ts index 34a671fd9ce5..f5ec7c7c74fb 100644 --- a/libs/api/domains/health-insurance/src/lib/graphql/accident-notification.resolver.ts +++ b/libs/api/domains/health-insurance/src/lib/graphql/accident-notification.resolver.ts @@ -2,7 +2,13 @@ import { Inject, UseGuards } from '@nestjs/common' import { Args, Query, Resolver } from '@nestjs/graphql' import { Audit } from '@island.is/nest/audit' import { ApiScope } from '@island.is/auth/scopes' -import { IdsUserGuard, Scopes, ScopesGuard } from '@island.is/auth-nest-tools' +import { + CurrentUser, + IdsUserGuard, + Scopes, + ScopesGuard, +} from '@island.is/auth-nest-tools' +import type { User as AuthUser } from '@island.is/auth-nest-tools' import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' import { HealthInsuranceAccidentStatusInput } from './dto/accidentStatus.input' @@ -27,10 +33,12 @@ export class HealthInsuranceAccidentNotificationResolver { async accidentStatus( @Args('input', { type: () => HealthInsuranceAccidentStatusInput }) input: HealthInsuranceAccidentStatusInput, + @CurrentUser() user: AuthUser, ): Promise { this.logger.debug(`Getting company information`) const accidentStatus = await this.accidentNotificationService.getAccidentNotificationStatus( + user, input.ihiDocumentID, ) this.logger.debug(`Getting accident status for id ${input.ihiDocumentID}`) diff --git a/libs/api/domains/health-insurance/src/lib/healthInsurance.module.ts b/libs/api/domains/health-insurance/src/lib/healthInsurance.module.ts index 504e527e4261..225222fb57a7 100644 --- a/libs/api/domains/health-insurance/src/lib/healthInsurance.module.ts +++ b/libs/api/domains/health-insurance/src/lib/healthInsurance.module.ts @@ -6,9 +6,13 @@ import { import { HealthInsuranceService } from './healthInsurance.service' import { HealthInsuranceV2ClientModule } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { AccidentNotificationService } from './accident-notification.service' +import { + AccidentreportsApi, + RightsPortalClientModule, +} from '@island.is/clients/icelandic-health-insurance/rights-portal' @Module({ - imports: [HealthInsuranceV2ClientModule], + imports: [HealthInsuranceV2ClientModule, RightsPortalClientModule], providers: [ HealthInsuranceService, AccidentNotificationService, diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification-v2.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification-v2.utils.ts new file mode 100644 index 000000000000..b2bb6cffe094 --- /dev/null +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification-v2.utils.ts @@ -0,0 +1,402 @@ +import { + AccidentNotificationAttachment, + AttachmentTypeEnum, +} from './types/attachments' +import { getValueViaPath } from '@island.is/application/core' +import { + AccidentDetailsV2, + AccidentNotificationAnswers, + AccidentTypeEnum, + ApplicantV2, + CompanyInfoV2, + FishermanWorkplaceAccidentLocationEnum, + FishingShipInfoV2, + GeneralWorkplaceAccidentLocationEnum, + HomeAccidentV2, + InjuredPersonInformationV2, + RepresentativeInfoV2, + StudiesAccidentTypeEnum, + WhoIsTheNotificationForEnum, + WorkAccidentTypeEnum, + WorkMachineV2, +} from '@island.is/application/templates/accident-notification' +import { YesOrNo } from '@island.is/application/types' +import { + MinarsidurAPIModelsAccidentReportsAccidentReportDTO, + MinarsidurAPIModelsAccidentReportsReporterDTO, + MinarsidurAPIModelsAccidentReportsInjuredDTO, + MinarsidurAPIModelsAccidentReportsAccidentDTO, + MinarsidurAPIModelsAccidentReportsEmployerDTO, + MinarsidurAPIModelsAccidentReportsClubDTO, + MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentDTO, + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum, + MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum, +} from '@island.is/clients/icelandic-health-insurance/rights-portal' + +export const applicationToAccidentReport = ( + answers: AccidentNotificationAnswers, + attachments: Array, +): MinarsidurAPIModelsAccidentReportsAccidentReportDTO => { + return { + reporter: getReporter(answers), + injured: getInjured(answers), + accident: getAccident(answers), + employer: getEmployer(answers), + club: getClub(answers), + attachments: getAttachments(attachments), + } +} + +const reportingForMap = { + [WhoIsTheNotificationForEnum.ME]: + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum.NUMBER_1, + [WhoIsTheNotificationForEnum.JURIDICALPERSON]: + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum.NUMBER_2, + [WhoIsTheNotificationForEnum.POWEROFATTORNEY]: + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum.NUMBER_3, + [WhoIsTheNotificationForEnum.CHILDINCUSTODY]: + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum.NUMBER_4, +} + +const whoIsTheNotificationForToDTO = (who: WhoIsTheNotificationForEnum) => { + return ( + reportingForMap[who] || + MinarsidurAPIModelsAccidentReportsReporterDTOReportingForEnum.NUMBER_1 + ) +} + +/* + * type can be: + * 4. Íþróttaslys, + * 6. Vinnuslys, + * 7. Heimilistrygging, + * 8. Björgunarmenn, + * 9. Nemendur við iðnnám + * + * type 6 can have the subtypes: + * 1. Almenn vinna á landi, + * 2. Vinna sjómanna, + * 3. Atvinnumennska í íþróttum, + * 4. Vinna við landbúnað + * + * type 9 can have the subtypes: + * 5. Starfsnám, + * 6. Verknám við háskóla, + * 7. Iðnám í löggildum iðngreinum + */ +const accidentTypeMap = { + [AccidentTypeEnum.SPORTS]: { type: 4 }, + [AccidentTypeEnum.WORK]: { type: 6 }, + [AccidentTypeEnum.HOMEACTIVITIES]: { type: 7 }, + [AccidentTypeEnum.RESCUEWORK]: { type: 8 }, + [AccidentTypeEnum.STUDIES]: { type: 9 }, +} + +const workAccidentSubtypeMap = { + [WorkAccidentTypeEnum.GENERAL]: 1, + [WorkAccidentTypeEnum.FISHERMAN]: 2, + [WorkAccidentTypeEnum.PROFESSIONALATHLETE]: 3, + [WorkAccidentTypeEnum.AGRICULTURE]: 4, +} + +const studiesAccidentSubtypeMap = { + [StudiesAccidentTypeEnum.INTERNSHIP]: 5, + [StudiesAccidentTypeEnum.APPRENTICESHIP]: 6, + [StudiesAccidentTypeEnum.VOCATIONALEDUCATION]: 7, +} + +const getAccidentTypes = (answers: AccidentNotificationAnswers) => { + const accidentType = getValueViaPath( + answers, + 'accidentType.answer', + ) as AccidentTypeEnum + const workAccidentType = getValueViaPath( + answers, + 'workAccident.type', + ) as WorkAccidentTypeEnum + const studiesAccidentType = getValueViaPath( + answers, + 'studiesAccident.type', + ) as StudiesAccidentTypeEnum + + return { accidentType, workAccidentType, studiesAccidentType } +} + +const accidentTypeToDTO = ( + answers: AccidentNotificationAnswers, +): { type: number; subtype?: number } => { + const { accidentType, workAccidentType, studiesAccidentType } = + getAccidentTypes(answers) + + const baseType = accidentTypeMap[accidentType] || { type: 6 } + + switch (accidentType) { + case AccidentTypeEnum.WORK: + return { + type: baseType.type, + subtype: workAccidentSubtypeMap[workAccidentType] || 1, + } + case AccidentTypeEnum.STUDIES: + return { + type: baseType.type, + subtype: studiesAccidentSubtypeMap[studiesAccidentType], + } + default: + return baseType + } +} + +const locationToDTO = (answers: AccidentNotificationAnswers) => { + const accidentLocation = getValueViaPath( + answers, + 'accidentLocation.answer', + ) as GeneralWorkplaceAccidentLocationEnum + + switch (accidentLocation) { + case GeneralWorkplaceAccidentLocationEnum.ATTHEWORKPLACE: + return 0 + case GeneralWorkplaceAccidentLocationEnum.TOORFROMTHEWORKPLACE: + return 1 + case GeneralWorkplaceAccidentLocationEnum.OTHER: + return 2 + default: + return 2 + } +} + +const shipLocationToDTO = (answers: AccidentNotificationAnswers) => { + const accidentLocation = getValueViaPath( + answers, + 'accidentLocation.answer', + ) as FishermanWorkplaceAccidentLocationEnum + + switch (accidentLocation) { + case FishermanWorkplaceAccidentLocationEnum.ONTHESHIP: + return 1 + case FishermanWorkplaceAccidentLocationEnum.TOORFROMTHEWORKPLACE: + return 2 + case FishermanWorkplaceAccidentLocationEnum.OTHER: + return 3 + default: + return 3 + } +} + +const getReporter = ( + answers: AccidentNotificationAnswers, +): MinarsidurAPIModelsAccidentReportsReporterDTO => { + const applicant = getValueViaPath(answers, 'applicant') as ApplicantV2 + const whoIsTheNotificationFor = getValueViaPath( + answers, + 'whoIsTheNotificationFor.answer', + ) as WhoIsTheNotificationForEnum + + const reportingFor = whoIsTheNotificationForToDTO(whoIsTheNotificationFor) + + const reporter = { + address: applicant.address ?? '', + city: applicant.city ?? '', + email: applicant.email ?? '', + name: applicant.name ?? '', + nationalId: applicant.nationalId ?? '', + phoneNumber: applicant.phoneNumber ?? '', + postcode: applicant.postalCode ?? '', + reportingFor, + } + + return reporter +} + +const getInjured = ( + answers: AccidentNotificationAnswers, +): MinarsidurAPIModelsAccidentReportsInjuredDTO => { + const whoIsTheNotificationFor = getValueViaPath( + answers, + 'whoIsTheNotificationFor.answer', + ) as WhoIsTheNotificationForEnum + + const injured = + whoIsTheNotificationFor === WhoIsTheNotificationForEnum.ME + ? { + ...(getValueViaPath(answers, 'applicant') as ApplicantV2), + jobTitle: getValueViaPath(answers, 'workAccident.jobTitle') as string, + } + : (getValueViaPath( + answers, + 'injuredPersonInformation', + ) as InjuredPersonInformationV2) + + return { + nationalId: injured.nationalId ?? '', + name: injured.name ?? '', + email: injured.email ?? '', + phone: injured.phoneNumber ?? '', + occupation: injured.jobTitle ?? '', + } +} + +const getAccident = ( + answers: AccidentNotificationAnswers, +): MinarsidurAPIModelsAccidentReportsAccidentDTO => { + const accidentType = accidentTypeToDTO(answers) + + const accidentDetails = getValueViaPath( + answers, + 'accidentDetails', + ) as AccidentDetailsV2 + + const fatal = getValueViaPath(answers, 'wasTheAccidentFatal') as YesOrNo + + const accidentLocation = locationToDTO(answers) + + return { + type: accidentType.type ?? null, + subtype: accidentType.subtype ?? null, + datetime: accidentDetails.dateOfAccident + ? new Date(accidentDetails.dateOfAccident) + : new Date(), + description: accidentDetails.descriptionOfAccident ?? '', + fatal: fatal === 'yes', + location: accidentLocation, + locationDescription: accidentDetails.descriptionOfAccident ?? '', + symptoms: accidentDetails.accidentSymptoms ?? '', + dateTimeOfDoctorVisit: accidentDetails.dateOfDoctorVisit + ? new Date(accidentDetails.dateOfDoctorVisit) + : new Date(), + // dockName: null, // Not in the application, but should it? + // dockGps: null, // Not in the application, but should it? + atHome: getAtHome(answers), + atWork: getAtWork(answers), + atSailorWork: getAtSailorWork(answers), + } +} + +const getAtHome = (answers: AccidentNotificationAnswers) => { + const homeAccident = getValueViaPath( + answers, + 'homeAccident', + ) as HomeAccidentV2 + + if (!homeAccident) { + return undefined + } + + return ( + homeAccident && { + address: homeAccident.address ?? '', + city: homeAccident.community ?? '', + postcode: homeAccident.postalCode ?? '', + comment: homeAccident.moreDetails ?? '', + } + ) +} + +const getAtWork = (answers: AccidentNotificationAnswers) => { + const workMachine = getValueViaPath(answers, 'workMachine') as WorkMachineV2 + + if (!workMachine || !workMachine?.descriptionOfMachine) { + return undefined + } + + return { machineDescription: workMachine.descriptionOfMachine } +} + +const getAtSailorWork = (answers: AccidentNotificationAnswers) => { + const shipLocation = shipLocationToDTO(answers) + const fishingShipInfo = getValueViaPath( + answers, + 'fishingShipInfo', + ) as FishingShipInfoV2 + + if (!shipLocation || !fishingShipInfo) { + return undefined + } + + return { + shipLocation: shipLocation, + shipName: fishingShipInfo.shipName ?? '', + shipDesignation: fishingShipInfo.shipCharacters ?? '', + shipHomePort: fishingShipInfo.homePort ?? '', + shipRegistryNumber: fishingShipInfo.shipRegisterNumber ?? '', + } +} + +const getEmployer = ( + answers: AccidentNotificationAnswers, +): MinarsidurAPIModelsAccidentReportsEmployerDTO | undefined => { + const companyInfo = getValueViaPath(answers, 'companyInfo') as CompanyInfoV2 + const accidentType = getValueViaPath( + answers, + 'accidentType.radioButton', + ) as AccidentTypeEnum + const representative = getValueViaPath( + answers, + 'representative', + ) as RepresentativeInfoV2 + + if (accidentType !== AccidentTypeEnum.WORK) return + + return { + companyName: companyInfo.name ?? '', + companyNationalId: companyInfo.nationalRegistrationId ?? '', + representativeName: representative.name ?? '', + representativeEmail: representative.email ?? '', + representativePhone: representative.phoneNumber ?? '', + } +} + +const getClub = ( + answers: AccidentNotificationAnswers, +): MinarsidurAPIModelsAccidentReportsClubDTO | undefined => { + const accidentType = getValueViaPath( + answers, + 'accidentType.radioButton', + ) as AccidentTypeEnum + if (accidentType !== AccidentTypeEnum.SPORTS) return + + const club = getValueViaPath(answers, 'companyInfo') as CompanyInfoV2 + const accidentLocation = getValueViaPath( + answers, + 'accidentLocation.answer', + ) as string + return { + nationalId: club.nationalRegistrationId ?? '', + name: club.name ?? '', + accidentType: accidentLocation ?? '', + } +} + +const getAttachments = ( + attachments: Array, +): Array => { + const mappedFiles = attachments.map((attachment) => { + const contentType = attachment.name.split('.').pop() + return { + type: attachment.attachmentType === 4 ? 2 : attachment.attachmentType, // Hack since additional files are not supported yet... + document: { + fileName: attachment.name, + contentType: contentType ?? undefined, + data: attachment.content, + }, + } + }) + + return mappedFiles +} + +export const mapAttachmentTypeToAccidentReportType = ( + attachmentType: AttachmentTypeEnum, +): MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum => { + switch (attachmentType) { + case AttachmentTypeEnum.INJURY_CERTIFICATE: + return MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum.NUMBER_1 + case AttachmentTypeEnum.POWER_OF_ATTORNEY: + return MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum.NUMBER_2 + case AttachmentTypeEnum.POLICE_REPORT: + return MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum.NUMBER_3 + case AttachmentTypeEnum.ADDITIONAL_FILES: + return MinarsidurAPIModelsAccidentReportsAccidentReportAttachmentTypeEnum.NUMBER_4 + default: + throw new Error('Unknown attachment type') + } +} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts index c3d21f0fbc8f..b32a8272cf51 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.module.ts @@ -3,11 +3,11 @@ import { SharedTemplateAPIModule } from '../../shared' import { BaseTemplateAPIModuleConfig } from '../../../types' import { ACCIDENT_NOTIFICATION_CONFIG } from './config' import { AccidentNotificationService } from './accident-notification.service' -import { HealthInsuranceV2ClientModule } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { ApplicationAttachmentService } from './attachments/applicationAttachment.service' import { AccidentNotificationAttachmentProvider } from './attachments/applicationAttachmentProvider' import { S3 } from 'aws-sdk' import { S3Service } from './attachments/s3.service' +import { RightsPortalClientModule } from '@island.is/clients/icelandic-health-insurance/rights-portal' const applicationRecipientName = process.env.ACCIDENT_NOTIFICATION_APPLICATION_RECIPIENT_NAME ?? '' @@ -26,7 +26,7 @@ export class AccidentNotificationModule { module: AccidentNotificationModule, imports: [ SharedTemplateAPIModule.register(config), - HealthInsuranceV2ClientModule, + RightsPortalClientModule, ], providers: [ { diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts index 55e60124b910..c103dabd04be 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.spec.ts @@ -1,9 +1,5 @@ import { Test } from '@nestjs/testing' import { ConfigService } from '@nestjs/config' -import { - ApplicationStatus, - ApplicationTypes, -} from '@island.is/application/types' import { logger, LOGGER_PROVIDER } from '@island.is/logging' import { EmailService } from '@island.is/email-service' import { SharedTemplateApiService } from '../../shared' @@ -12,16 +8,21 @@ import { AccidentNotificationService } from './accident-notification.service' import { AccidentNotificationAttachmentProvider } from './attachments/applicationAttachmentProvider' import { ApplicationAttachmentService } from './attachments/applicationAttachment.service' import { ACCIDENT_NOTIFICATION_CONFIG } from './config' -import { DocumentApi } from '@island.is/clients/icelandic-health-insurance/health-insurance' -import { createCurrentUser } from '@island.is/testing/fixtures' import { S3 } from 'aws-sdk' -import type { Locale } from '@island.is/shared/types' -import { createApplication } from '@island.is/application/testing' -import get from 'lodash/get' -import set from 'lodash/set' import { S3Service } from './attachments/s3.service' import { SmsService } from '@island.is/nova-sms' import { PaymentService } from '@island.is/application/api/payment' +import { AccidentreportsApi } from '@island.is/clients/icelandic-health-insurance/rights-portal' +import { createApplication } from '@island.is/application/testing' +import { createCurrentUser } from '@island.is/testing/fixtures' +import get from 'lodash/get' +import set from 'lodash/set' +import type { Locale } from '@island.is/shared/types' +import { + ApplicationStatus, + ApplicationTypes, +} from '@island.is/application/types' + const nationalId = '1234564321' let id = 0 @@ -58,7 +59,7 @@ const S3Instance = { describe('AccidentNotificationService', () => { let accidentNotificationService: AccidentNotificationService let s3Service: S3Service - let documentApi: DocumentApi + let accidentReportsApi: AccidentreportsApi beforeEach(async () => { const module = await Test.createTestingModule({ @@ -70,14 +71,40 @@ describe('AccidentNotificationService', () => { }, ApplicationAttachmentService, { - provide: DocumentApi, + provide: AccidentreportsApi, useClass: jest.fn(() => ({ - documentDocumentAttachment: () => { - Promise.resolve({ - success: 1, - numberIHI: 12313, - }) - }, + submitAccidentReportAttachment: jest + .fn() + .mockImplementation((input) => { + const { document } = + input.minarsidurAPIModelsAccidentReportsAccidentReportAttachmentDTO + const { fileName } = document + + if (fileName === 'somekey1_averkavottord') { + return Promise.resolve({ + requestId: + '290f493c44f5d63d06b374d0a5abd292fae38b92cab2fae5efefe1b0e9347f56', + success: true, + errorMessage: null, + }) + } else if (fileName === 'somekey2_logregluskyrsla') { + return Promise.resolve({ + requestId: + 'c28cbc8c78fbe6ee77df1fced5f03ffb6e367458f5f094c4685e41b5b31d06fb', + success: true, + errorMessage: null, + }) + } + + return Promise.resolve({ + requestId: 'default-request-id', + success: true, + errorMessage: null, + }) + }), + addAttachment: jest.fn().mockResolvedValue({ success: true }), + getAttachmentStatus: jest.fn().mockResolvedValue({ status: 'ok' }), + withMiddleware: jest.fn().mockReturnThis(), })), }, { @@ -90,7 +117,7 @@ describe('AccidentNotificationService', () => { }, { provide: PaymentService, - useValue: {}, //not used + useValue: {}, // not used }, { provide: EmailService, @@ -126,7 +153,7 @@ describe('AccidentNotificationService', () => { accidentNotificationService = module.get(AccidentNotificationService) s3Service = module.get(S3Service) - documentApi = module.get(DocumentApi) + accidentReportsApi = module.get(AccidentreportsApi) }) describe('addAdditionalAttachment', () => { @@ -226,7 +253,10 @@ describe('AccidentNotificationService', () => { .mockResolvedValueOnce( Buffer.from('some dsfsf', 'utf-8') as unknown as string, ) - const send = jest.spyOn(documentApi, 'documentDocumentAttachment') + const send = jest.spyOn( + accidentReportsApi, + 'submitAccidentReportAttachment', + ) const res = await accidentNotificationService.addAdditionalAttachment( props, ) @@ -254,8 +284,6 @@ describe('AccidentNotificationService', () => { props, ) - //response should be identical to earlier response - expect(send).toHaveBeenCalledTimes(2) expect(res2).toEqual({ sentDocuments: sentDocs, }) diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.ts index 411baed14144..65004cab82e2 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.service.ts @@ -1,20 +1,14 @@ -import { getValueViaPath } from '@island.is/application/core' import { AccidentNotificationAnswers, ReviewApprovalEnum, utils, } from '@island.is/application/templates/accident-notification' -import { DocumentApi } from '@island.is/clients/icelandic-health-insurance/health-insurance' import { LOGGER_PROVIDER } from '@island.is/logging' import type { Logger } from '@island.is/logging' import { Inject, Injectable } from '@nestjs/common' import { TemplateApiModuleActionProps } from '../../../types' import { SharedTemplateApiService } from '../../shared' -import { - applicationAnswersToXml, - getApplicationDocumentId, - whiteListedErrorCodes, -} from './accident-notification.utils' +import { whiteListedErrorCodes } from './accident-notification.utils' import type { AccidentNotificationConfig } from './config' import { ACCIDENT_NOTIFICATION_CONFIG } from './config' import { @@ -24,13 +18,19 @@ import { import { AccidentNotificationAttachmentProvider } from './attachments/applicationAttachmentProvider' import { attachmentStatusToAttachmentRequests, - filterOutAlreadySentDocuments, getAddAttachmentSentDocumentHashList, - getApplicationAttachmentStatus, + getNewAttachments, + getReportId, + getReviewApplicationData, } from './attachments/attachment.utils' -import { AccidentNotificationAttachment } from './types/attachments' import { BaseTemplateApiService } from '../../base-template-api.service' import { ApplicationTypes } from '@island.is/application/types' +import { + applicationToAccidentReport, + mapAttachmentTypeToAccidentReportType, +} from './accident-notification-v2.utils' +import { AccidentreportsApi } from '@island.is/clients/icelandic-health-insurance/rights-portal' +import { Auth, AuthMiddleware } from '@island.is/auth-nest-tools' const SIX_MONTHS_IN_SECONDS_EXPIRES = 6 * 30 * 24 * 60 * 60 @@ -42,28 +42,33 @@ export class AccidentNotificationService extends BaseTemplateApiService { private accidentConfig: AccidentNotificationConfig, private readonly sharedTemplateAPIService: SharedTemplateApiService, private readonly attachmentProvider: AccidentNotificationAttachmentProvider, - private readonly documentApi: DocumentApi, + private readonly accidentReportsApi: AccidentreportsApi, ) { super(ApplicationTypes.ACCIDENT_NOTIFICATION) } - async submitApplication({ application }: TemplateApiModuleActionProps) { + private accidentsReportsApiWithAuth(auth: Auth) { + return this.accidentReportsApi.withMiddleware(new AuthMiddleware(auth)) + } + + async submitApplication({ application, auth }: TemplateApiModuleActionProps) { try { const requests = attachmentStatusToAttachmentRequests() - const attachments = await this.attachmentProvider.getFiles( requests, application, ) const fileHashList = attachments.map((attachment) => attachment.hash) - const answers = application.answers as AccidentNotificationAnswers - const xml = applicationAnswersToXml(answers, attachments) + const accidentReport = applicationToAccidentReport(answers, attachments) - const { ihiDocumentID } = await this.documentApi.documentPost({ - document: { doc: xml, documentType: 801 }, + const res = await this.accidentsReportsApiWithAuth( + auth, + ).submitAccidentReport({ + minarsidurAPIModelsAccidentReportsAccidentReportDTO: accidentReport, }) + const reportId = res.reportId await this.sharedTemplateAPIService.sendEmail( (props) => @@ -71,7 +76,7 @@ export class AccidentNotificationService extends BaseTemplateApiService { props, this.accidentConfig.applicationSenderName, this.accidentConfig.applicationSenderEmail, - ihiDocumentID, + reportId ?? undefined, ), application, ) @@ -85,13 +90,17 @@ export class AccidentNotificationService extends BaseTemplateApiService { await this.sharedTemplateAPIService.assignApplicationThroughEmail( (props, assignLink) => - generateAssignReviewerEmail(props, assignLink, ihiDocumentID), + generateAssignReviewerEmail( + props, + assignLink, + reportId ?? undefined, + ), application, token, ) } return { - documentId: ihiDocumentID, + documentId: reportId, sentDocuments: fileHashList, } } catch (e) { @@ -115,80 +124,78 @@ export class AccidentNotificationService extends BaseTemplateApiService { } } - async addAdditionalAttachment({ application }: TemplateApiModuleActionProps) { - const attachmentStatus = getApplicationAttachmentStatus(application) - const requests = attachmentStatusToAttachmentRequests(attachmentStatus) + async addAdditionalAttachment({ + application, + auth, + }: TemplateApiModuleActionProps) { + const reportId = getReportId(application) - const attachments = await this.attachmentProvider.getFiles( - requests, - application, - ) + if (!reportId) { + throw new Error( + 'Villa kom upp við vistun á umsókn. Skjalanúmer fannst ekki.', + ) + } - const newAttachments = filterOutAlreadySentDocuments( - attachments, + const newAttachments = await getNewAttachments( application, + this.attachmentProvider, ) - const documentId = getApplicationDocumentId(application) + let successfulAttachments = [] - const promises = newAttachments.map((attachment) => - this.sendAttachment(attachment, documentId), - ) + try { + const promises = newAttachments.map((attachment) => { + const attachmentType = mapAttachmentTypeToAccidentReportType( + attachment.attachmentType, + ) + const contentType = attachment.name.split('.').pop() + return this.accidentsReportsApiWithAuth( + auth, + ).submitAccidentReportAttachment({ + reportId, + minarsidurAPIModelsAccidentReportsAccidentReportAttachmentDTO: { + type: attachmentType, + document: { + fileName: attachment.name, + contentType, + data: attachment.content, + }, + }, + }) + }) - const successfulAttachments = (await Promise.all(promises)).filter( - (x) => x !== null, - ) + successfulAttachments = (await Promise.all(promises)).filter( + (x) => x !== null, + ) + } catch (e) { + this.logger.error('Error adding additional attachment', e) + throw new Error('Villa kom upp við að bæta við viðbótarskjali.') + } return { sentDocuments: [ ...getAddAttachmentSentDocumentHashList(application), - ...successfulAttachments, + ...successfulAttachments.map((attachment) => attachment.requestId), ], } } - /** - * Sends the attachment to SÍ and returns the document hash on success and null on failure - * @param attachment attachment to send - */ - private async sendAttachment( - attachment: AccidentNotificationAttachment, - documentId: number, - ): Promise { - try { - await this.documentApi.documentDocumentAttachment({ - documentAttachment: { - attachmentBody: attachment.content, - attachmentType: attachment.attachmentType, - title: attachment.name, - }, - ihiDocumentID: documentId, - }) - return attachment.hash - } catch (e) { - this.logger.error('Error sending document to SÍ', e) - return null - } - } - - async reviewApplication({ application }: TemplateApiModuleActionProps) { - const documentId = getApplicationDocumentId(application) - - const isRepresentativeOfCompanyOrInstitute = - utils.isRepresentativeOfCompanyOrInstitute(application.answers) - const reviewApproval = getValueViaPath( - application.answers, - 'reviewApproval', - ) as ReviewApprovalEnum - const reviewComment = - getValueViaPath(application.answers, 'reviewComment') || '' - await this.documentApi.documentSendConfirmation({ - ihiDocumentID: documentId, - confirmationIN: { - confirmationType: - reviewApproval === ReviewApprovalEnum.APPROVED ? 1 : 2, - confirmationParty: isRepresentativeOfCompanyOrInstitute ? 1 : 2, - objection: reviewComment as string, + async reviewApplication({ application, auth }: TemplateApiModuleActionProps) { + const { + documentId, + isRepresentativeOfCompanyOrInstitute, + reviewApproval, + reviewComment, + } = getReviewApplicationData(application) + + await this.accidentsReportsApiWithAuth( + auth, + ).submitAccidentReportConfirmation({ + reportId: documentId, + minarsidurAPIModelsAccidentReportsAccidentReportConfirmationDTO: { + party: isRepresentativeOfCompanyOrInstitute ? 1 : 2, + accepted: reviewApproval === ReviewApprovalEnum.APPROVED, + comment: reviewComment, }, }) } diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.spec.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.spec.ts index b333bfc20798..dd4f0d6a462b 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.spec.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.spec.ts @@ -1,188 +1,5 @@ -import { - AccidentNotificationAnswers, - AccidentTypeEnum, - AttachmentsEnum, - FishermanWorkplaceAccidentShipLocationEnum, - PowerOfAttorneyUploadEnum, - ReviewApprovalEnum, - StudiesAccidentTypeEnum, - WhoIsTheNotificationForEnum, - WorkAccidentTypeEnum, - OnBehalf, - StudiesAccidentLocationEnum, -} from '@island.is/application/templates/accident-notification' import * as utils from './accident-notification.utils' -import { - AccidentNotificationAttachment, - AttachmentTypeEnum, -} from './types/attachments' -import { Application, FormValue } from '@island.is/application/types' - -describe('applicationAnswersToXml', () => { - it.each([ - AccidentTypeEnum.SPORTS, - AccidentTypeEnum.RESCUEWORK, - AccidentTypeEnum.STUDIES, - ])( - 'should map correctly for a person reporting an injury for themselves, injured in sports, rescue work or studies, with no employer involved', - (accidentType) => { - const attachments = getDefaultAttachments() - const answers = getFullBasicAnswers() - - answers.accidentType.radioButton = accidentType - const companyInfoCache = answers.companyInfo - answers.companyInfo = undefined - - const result = utils.applicationAnswersToXml(answers, attachments) - - expect(result.includes(answers.applicant.name)).toBeTruthy() - expect(result.includes(answers.applicant.phoneNumber)).toBeTruthy() - expect(result.includes(answers.applicant.nationalId)).toBeTruthy() - expect( - result.includes(answers.accidentDetails.dateOfAccident), - ).toBeTruthy() - expect( - result.includes((answers as any)['locationAndPurpose?'].location), - ).toBeTruthy() - expect(result.includes(attachments[0].name)).toBeTruthy() - expect(result.includes(attachments[1].name)).toBeTruthy() - - expect(result.includes(answers.childInCustody.name)).toBeFalsy() - expect(result.includes(answers.injuredPersonInformation.name)).toBeFalsy() - expect(result.includes(companyInfoCache?.name as string)).toBeFalsy() - }, - ) - - it.each([ - { type: AccidentTypeEnum.HOMEACTIVITIES, fisherman: false }, - { type: AccidentTypeEnum.WORK, fisherman: false }, - { type: AccidentTypeEnum.WORK, fisherman: true }, - ])( - 'should map correctly for a person reporting an injury for themselves, injured in at home or at work, with employer involved', - (data) => { - const attachments = getDefaultAttachments() - const answers = getFullBasicAnswers() - - answers.accidentType.radioButton = data.type - answers.workMachineRadio = 'yes' - if (data.fisherman) - answers.workAccident.type = WorkAccidentTypeEnum.FISHERMAN - - const result = utils.applicationAnswersToXml(answers, attachments) - - expect(result.includes(answers.applicant.name)).toBeTruthy() - expect(result.includes(answers.applicant.phoneNumber)).toBeTruthy() - expect(result.includes(answers.applicant.nationalId)).toBeTruthy() - expect( - result.includes(answers.accidentDetails.dateOfAccident), - ).toBeTruthy() - expect( - result.includes((answers as any)['locationAndPurpose?'].location), - ).toBeTruthy() - expect(result.includes(attachments[0].name)).toBeTruthy() - expect(result.includes(attachments[1].name)).toBeTruthy() - - expect(result.includes(answers.childInCustody.name)).toBeFalsy() - expect(result.includes(answers.injuredPersonInformation.name)).toBeFalsy() - - const isHomeAccident = data.type === AccidentTypeEnum.HOMEACTIVITIES - expect(result.includes(answers.companyInfo?.name as string)).toBe( - !isHomeAccident, - ) - expect(result.includes(answers.homeAccident?.address)).toBe( - isHomeAccident, - ) - expect(result.includes(answers.homeAccident?.postalCode)).toBe( - isHomeAccident, - ) - expect(result.includes(answers.homeAccident?.community)).toBe( - isHomeAccident, - ) - expect(result.includes(answers.homeAccident?.moreDetails as string)).toBe( - isHomeAccident, - ) - - if (!isHomeAccident) { - expect(result.includes(answers.fishingShipInfo?.shipName)).toBe( - data.fisherman, - ) - expect(result.includes(answers.fishingShipInfo?.shipCharacters)).toBe( - data.fisherman, - ) - expect(result.includes(answers.workMachine?.descriptionOfMachine)).toBe( - !data.fisherman, - ) - } - }, - ) - - it.each([ - { - for: WhoIsTheNotificationForEnum.ME, - someoneElse: false, - shouldHaveCompanyInfo: false, - }, - { - for: WhoIsTheNotificationForEnum.POWEROFATTORNEY, - someoneElse: true, - shouldHaveCompanyInfo: false, - }, - { - for: WhoIsTheNotificationForEnum.JURIDICALPERSON, - someoneElse: true, - shouldHaveCompanyInfo: true, - }, - { - for: WhoIsTheNotificationForEnum.CHILDINCUSTODY, - someoneElse: true, - shouldHaveCompanyInfo: false, - }, - ])( - 'should map correctly for a person reporting an injury for themselves, someone else, juridical person, or their child, with no employer involved', - (data) => { - const attachments = getDefaultAttachments() - const answers = getFullBasicAnswers() - answers.whoIsTheNotificationFor.answer = data.for - const companyInfoCache = answers.companyInfo - answers.companyInfo = undefined - - const result = utils.applicationAnswersToXml(answers, attachments) - - expect(result.includes(answers.applicant.name)).toBeTruthy() - if (data.someoneElse) { - data.for === WhoIsTheNotificationForEnum.CHILDINCUSTODY - ? expect( - result.includes(answers.childInCustody.name) && - !result.includes(answers.injuredPersonInformation.name), - ).toBeTruthy() - : expect( - result.includes(answers.injuredPersonInformation.name) && - !result.includes(answers.childInCustody.name), - ).toBeTruthy() - - data.for === WhoIsTheNotificationForEnum.JURIDICALPERSON - ? expect( - result.includes(answers.juridicalPerson.companyName), - ).toBeTruthy() - : expect( - result.includes(answers.juridicalPerson.companyName), - ).toBeFalsy() - } - expect(result.includes(answers.applicant.phoneNumber)).toBeTruthy() - expect(result.includes(answers.applicant.nationalId)).toBeTruthy() - expect( - result.includes(answers.accidentDetails.dateOfAccident), - ).toBeTruthy() - expect( - result.includes((answers as any)['locationAndPurpose?'].location), - ).toBeTruthy() - expect(result.includes(attachments[0].name)).toBeTruthy() - expect(result.includes(attachments[1].name)).toBeTruthy() - - expect(result.includes(companyInfoCache?.name as string)).toBeFalsy() - }, - ) -}) +import { Application } from '@island.is/application/types' const createMockPartialApplication = (documentId?: number): Application => ({ @@ -212,158 +29,3 @@ describe('getApplicationDocumentId', () => { ) }) }) - -const getDefaultAttachments = (): AccidentNotificationAttachment[] => { - return [ - { - content: 'InjurcyCertDocContent', - attachmentType: AttachmentTypeEnum.INJURY_CERTIFICATE, - name: 'InjurcyCertDoc.pdf', - hash: 'InjurcyCertDocHash', - }, - { - content: 'PoliceReportDocContent', - attachmentType: AttachmentTypeEnum.POLICE_REPORT, - name: 'PoliceReportDoc.pdf', - hash: 'PoliceReportDocHash', - }, - ] -} - -const getFullBasicAnswers = (): AccidentNotificationAnswers => { - return { - applicant: { - name: 'Fullname Fullname', - nationalId: '1234564321', - email: 'mock@mockemail.com', - phoneNumber: '7654321', - address: 'Bær 1', - city: 'Reykjavik', - postalCode: '101', - }, - whoIsTheNotificationFor: { - answer: WhoIsTheNotificationForEnum.ME, - }, - childInCustody: { - name: 'Some Kid', - nationalId: '1234564332', - }, - injuredPersonInformation: { - name: 'Fullername Fullername', - nationalId: '1234564322', - email: 'mockmock@mockemail.com', - phoneNumber: '7654322', - jobTitle: '', - }, - accidentType: { - radioButton: AccidentTypeEnum.SPORTS, - }, - accidentDetails: { - dateOfAccident: '1999-09-19', - timeOfAccident: '18:00:00', - descriptionOfAccident: 'Hurt', - accidentSymptoms: 'Pain', - isHealthInsured: 'yes', - dateOfDoctorVisit: '', - timeOfDoctorVisit: '', - }, - homeAccident: { - address: 'homeAccidentAddress123', - postalCode: 'homeAccidentAddress123PortalCode100', - community: 'wtfIsCommunityHere?Kopavogur', - moreDetails: 'more detail about home accident', - }, - workAccident: { - type: WorkAccidentTypeEnum.GENERAL, - jobTitle: '', - }, - wasTheAccidentFatal: 'no', - carAccidentHindrance: 'no', - // getValueViaPath can find this when this object isnt types but is stringified - // so we need to have this as a string to accomidate the ? when using the typed version - 'locationAndPurpose?': { - location: 'Somewhere', - }, - accidentLocation: { - answer: StudiesAccidentLocationEnum.OTHER, - }, - shipLocation: { - answer: FishermanWorkplaceAccidentShipLocationEnum.SAILINGORFISHING, - }, - fishingShipInfo: { - shipName: 'SomeShipName', - shipCharacters: 'SomeShipCharacters', - homePort: 'SomeHomePort', - shipRegisterNumber: 'ShomeShipRegNr123', - }, - workMachineRadio: 'yes', - workMachine: { - descriptionOfMachine: 'description of machine that messed someone up', - }, - companyInfo: { - nationalRegistrationId: '7654324321', - name: 'Company EHF', - }, - representative: { - name: 'Some Guy', - nationalId: '6543214321', - email: 'rep@mockemail.com', - phoneNumber: '1111111', - }, - approveExternalData: false, - externalData: { - nationalRegistry: { - status: 'success', - data: { - nationalId: '', - address: { - locality: '', - municipalityCode: '', - postalCode: '', - streetAddress: '', - }, - age: 0, - citizenship: { - code: '', - name: '', - }, - fullName: '', - }, - date: '', - }, - userProfile: { - data: { - email: '', - mobilePhoneNumber: '', - }, - }, - }, - info: { - onBehalf: OnBehalf.MYSELF, - }, - timePassedHindrance: 'yes', - injuryCertificate: { - answer: AttachmentsEnum.INJURYCERTIFICATE, - }, - additionalAttachments: { - answer: AttachmentsEnum.ADDITIONALLATER, - }, - attachments: {}, - fatalAccidentUploadDeathCertificateNow: 'yes', - onPayRoll: { - answer: 'yes', - }, - studiesAccident: { - type: StudiesAccidentTypeEnum.INTERNSHIP, - }, - juridicalPerson: { - companyName: 'juridicalPersonCompany', - companyNationalId: '7777778888', - companyConfirmation: [], - }, - powerOfAttorney: { - type: PowerOfAttorneyUploadEnum.UPLOADLATER, - }, - reviewApproval: ReviewApprovalEnum.NOTREVIEWED, - } as unknown as AccidentNotificationAnswers -} diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.ts index 6a03ad2436f6..e668fcce2662 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/accident-notification.utils.ts @@ -1,31 +1,7 @@ -import { getValueViaPath } from '@island.is/application/core' import { Application } from '@island.is/application/types' -import { - accidentLocationLabelMapper, - AccidentNotificationAnswers, - AccidentTypeEnum, - CompanyInfo, - FishermanWorkplaceAccidentShipLocationEnum, - RepresentativeInfo, - StudiesAccidentTypeEnum, - SubmittedApplicationData, - WhoIsTheNotificationForEnum, - WorkAccidentTypeEnum, - Applicant, - YesOrNo, - utils, -} from '@island.is/application/templates/accident-notification' +import { SubmittedApplicationData } from '@island.is/application/templates/accident-notification' import { isRunningOnEnvironment } from '@island.is/shared/utils' import { join } from 'path' -import { - ApplicationSubmit, - Atvinnurekandi, - Slys, - TilkynnandiOrSlasadi, -} from './types/applicationSubmit' -import { AccidentNotificationAttachment } from './types/attachments' -import { objectToXML } from '../../shared/shared.utils' -import { parse } from 'libphonenumber-js' export const pathToAsset = (file: string) => { if (isRunningOnEnvironment('local')) { @@ -38,64 +14,6 @@ export const pathToAsset = (file: string) => { return join(__dirname, `./accident-notification-assets/${file}`) } -/** - * Generates Xml correctly formatted for each type of SÍ application - * The order of the elements is important as an incorrect order - * will result in an Invalid Xml error from SÍ - * @param answers - The answers to the accident notification - * @param attachments - The attachments names and base64 Content - * @returns The application Xml correctly formatted for SÍ application - */ -export const applicationAnswersToXml = ( - answers: AccidentNotificationAnswers, - attachments: AccidentNotificationAttachment[], -): string => { - const fylgiskjol = { - fylgiskjol: { - fylgiskjal: attachments.map((attachment) => { - return { - heiti: attachment.name, - tegund: attachment.attachmentType as number, - innihald: attachment.content, - } - }), - }, - } - - const phone = getValueViaPath(answers, 'applicant.phoneNumber') as string - const parsedPhone = parse(phone ?? '') - - const applicationJson: ApplicationSubmit = { - slysatilkynning: { - tilkynnandi: { - kennitala: getValueViaPath(answers, '.applicant.nationalId') as string, - nafn: getValueViaPath(answers, '.applicant.name') as string, - heimili: getValueViaPath(answers, 'applicant.address') as string, - stadur: getValueViaPath(answers, 'applicant.city') as string, - postfang: getValueViaPath(answers, 'applicant.postalCode') as string, - netfang: getValueViaPath(answers, 'applicant.email') as string, - fyrirhvernerveridadtilkynna: whoIsTheNotificationForToId( - getValueViaPath( - answers, - 'whoIsTheNotificationFor.answer', - ) as WhoIsTheNotificationForEnum, - ), - simi: parsedPhone.phone?.toString() || phone, - }, - slasadi: injuredPerson(answers), - slys: accident(answers), - atvinnurekandi: employer(answers), - ...fylgiskjol, - }, - } - - const xml = `${objectToXML( - applicationJson, - )}` - - return xml -} - export const whiteListedErrorCodes = [ 's801_fyrirt_kennit', 's801_undirtegund', @@ -109,285 +27,6 @@ export const whiteListedErrorCodes = [ 's801_slasadi', ] -const whoIsTheNotificationForToId = ( - value: WhoIsTheNotificationForEnum, -): number => { - switch (value) { - case WhoIsTheNotificationForEnum.ME: - return 1 - case WhoIsTheNotificationForEnum.POWEROFATTORNEY: - return 2 - case WhoIsTheNotificationForEnum.JURIDICALPERSON: - return 3 - case WhoIsTheNotificationForEnum.CHILDINCUSTODY: - return 4 - } -} - -const injuredPerson = ( - answers: AccidentNotificationAnswers, -): TilkynnandiOrSlasadi => { - const whoIsTheNotificationFor = getValueViaPath( - answers, - 'whoIsTheNotificationFor.answer', - ) as WhoIsTheNotificationForEnum - if (whoIsTheNotificationFor === WhoIsTheNotificationForEnum.CHILDINCUSTODY) { - return { - kennitala: getValueViaPath( - answers, - 'childInCustody.nationalId', - ) as string, - nafn: getValueViaPath(answers, 'childInCustody.name') as string, - netfang: ' ', //the child has no email, - } - } - const person = - whoIsTheNotificationFor === WhoIsTheNotificationForEnum.ME - ? (getValueViaPath(answers, 'applicant') as Applicant) - : (getValueViaPath(answers, 'injuredPersonInformation') as Applicant) - return { - kennitala: person.nationalId, - nafn: person.name, - netfang: person.email, - simi: person.phoneNumber, - } -} - -const accident = (answers: AccidentNotificationAnswers): Slys => { - const accidentType = accidentTypeToId( - getValueViaPath(answers, 'accidentType.radioButton') as AccidentTypeEnum, - ) - const accidentBase = { - tegund: accidentType, - undirtegund: determineSubType(answers), - dagsetningslys: getValueViaPath( - answers, - 'accidentDetails.dateOfAccident', - ) as string, - timislys: getValueViaPath( - answers, - 'accidentDetails.timeOfAccident', - ) as string, - lysing: getValueViaPath( - answers, - 'accidentDetails.descriptionOfAccident', - ) as string, - banaslys: yesOrNoToNumber( - getValueViaPath(answers, 'wasTheAccidentFatal') as YesOrNo, - ), - bilslys: yesOrNoToNumber( - getValueViaPath(answers, 'carAccidentHindrance') as YesOrNo, - ), - stadurslysseferindi: - (getValueViaPath(answers, 'locationAndPurpose?.location') as string) ?? - '', - lysingerindis: (getValueViaPath(answers, 'accidentLocation') as string) - ? accidentLocationLabelMapper( - getValueViaPath( - answers, - 'accidentLocation.answer', - ) as keyof typeof accidentLocationLabelMapper, - ) - : undefined, - } - - switch ( - getValueViaPath(answers, 'accidentType.radioButton') as AccidentTypeEnum - ) { - case AccidentTypeEnum.HOMEACTIVITIES: { - return { - ...accidentBase, - slysvidheimilisstorf: { - heimili: getValueViaPath(answers, 'homeAccident.address') as string, - postnumer: getValueViaPath( - answers, - 'homeAccident.postalCode', - ) as string, - sveitarfelag: getValueViaPath( - answers, - 'homeAccident.community', - ) as string, - nanar: getValueViaPath(answers, 'homeAccident.moreDetails') as string, - }, - } - } - case AccidentTypeEnum.SPORTS: - return accidentBase - case AccidentTypeEnum.RESCUEWORK: - return accidentBase - case AccidentTypeEnum.STUDIES: - return accidentBase - case AccidentTypeEnum.WORK: { - return work(answers, accidentBase) - } - } -} - -const work = ( - answers: AccidentNotificationAnswers, - accidentBase: Slys, -): Slys => { - const workAccidentType = getValueViaPath( - answers, - 'workAccident.type', - ) as WorkAccidentTypeEnum - if (workAccidentType === WorkAccidentTypeEnum.FISHERMAN) { - return { - ...accidentBase, - slysvidvinnusjomanna: { - stadsetningskips: shipLocation(answers), - nafnskips: getValueViaPath( - answers, - 'fishingShipInfo.shipName', - ) as string, - einkennisstafirskips: getValueViaPath( - answers, - 'fishingShipInfo.shipCharacters', - ) as string, - }, - } - } - - return (getValueViaPath(answers, 'workMachineRadio') as YesOrNo) === 'yes' - ? { - ...accidentBase, - slysvidvinnu: { - lysingavinnuvel: getValueViaPath( - answers, - 'workMachine.descriptionOfMachine', - ) as string, - }, - } - : accidentBase -} - -const shipLocation = (answers: AccidentNotificationAnswers): number => { - const location = getValueViaPath( - answers, - 'shipLocation.answer', - ) as FishermanWorkplaceAccidentShipLocationEnum - switch (location) { - case FishermanWorkplaceAccidentShipLocationEnum.SAILINGORFISHING: - return 1 - case FishermanWorkplaceAccidentShipLocationEnum.HARBOR: - return 2 - case FishermanWorkplaceAccidentShipLocationEnum.OTHER: - return 3 - } -} - -const employer = ( - answers: AccidentNotificationAnswers, -): Atvinnurekandi | undefined => { - const companyInfo = getValueViaPath(answers, 'companyInfo') as CompanyInfo - const representative = getValueViaPath( - answers, - 'representative', - ) as RepresentativeInfo - - // If the juridical person is reporting the company info is the juridical persons information - if ( - answers.juridicalPerson && - answers.applicant && - utils.isRepresentativeOfCompanyOrInstitute(answers) - ) { - return { - fyrirtaekikennitala: answers.juridicalPerson.companyNationalId, - fyrirtaekinafn: answers.juridicalPerson.companyName, - forsjaradilinafn: answers.applicant.name, - forsjaradilinetfang: answers.applicant.email, - forsjaradilisimi: answers.applicant.phoneNumber || '', - } - } - - if ( - (getValueViaPath( - answers, - 'accidentType.radioButton', - ) as AccidentTypeEnum) === AccidentTypeEnum.HOMEACTIVITIES || - !companyInfo || - !representative - ) { - return undefined - } - - return { - fyrirtaekikennitala: companyInfo.nationalRegistrationId, - fyrirtaekinafn: companyInfo.name, - forsjaradilinafn: representative.name, - forsjaradilinetfang: representative.email, - forsjaradilisimi: representative.phoneNumber || '', - } -} - -const yesOrNoToNumber = (value: string): number => { - return value === 'yes' ? 1 : 0 -} - -const accidentTypeToId = (typeEnum: AccidentTypeEnum): number => { - switch (typeEnum) { - case AccidentTypeEnum.HOMEACTIVITIES: - return 7 - case AccidentTypeEnum.SPORTS: - return 4 - case AccidentTypeEnum.RESCUEWORK: - return 8 - case AccidentTypeEnum.STUDIES: - return 9 - case AccidentTypeEnum.WORK: - return 6 - } -} - -const determineSubType = ( - answers: AccidentNotificationAnswers, -): number | undefined => { - if ( - (getValueViaPath( - answers, - 'accidentType.radioButton', - ) as AccidentTypeEnum) === AccidentTypeEnum.WORK - ) { - switch ( - getValueViaPath(answers, 'workAccident.type') as WorkAccidentTypeEnum - ) { - case WorkAccidentTypeEnum.GENERAL: - return 1 - case WorkAccidentTypeEnum.FISHERMAN: - return 2 - case WorkAccidentTypeEnum.PROFESSIONALATHLETE: - return 3 - case WorkAccidentTypeEnum.AGRICULTURE: - return 4 - default: - return undefined - } - } - if ( - (getValueViaPath( - answers, - 'accidentType.radioButton', - ) as AccidentTypeEnum) === AccidentTypeEnum.STUDIES - ) { - switch ( - getValueViaPath( - answers, - 'studiesAccident.type', - ) as StudiesAccidentTypeEnum - ) { - case StudiesAccidentTypeEnum.INTERNSHIP: - return 5 - case StudiesAccidentTypeEnum.VOCATIONALEDUCATION: - return 6 - case StudiesAccidentTypeEnum.APPRENTICESHIP: - return 7 - default: - return undefined - } - } - return undefined -} - export const getApplicationDocumentId = (application: Application): number => { const subAppData = application.externalData .submitApplication as SubmittedApplicationData diff --git a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/attachment.utils.ts b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/attachment.utils.ts index e756cbb8c06e..5539e060db2c 100644 --- a/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/attachment.utils.ts +++ b/libs/application/template-api-modules/src/lib/modules/templates/accident-notification/attachments/attachment.utils.ts @@ -1,7 +1,12 @@ import { getValueViaPath } from '@island.is/application/core' -import { Application } from '@island.is/application/types' import { + Application, + ApplicationWithAttachments, +} from '@island.is/application/types' +import { + AccidentNotificationAnswers, ReviewAddAttachmentData, + ReviewApprovalEnum, SubmittedApplicationData, } from '@island.is/application/templates/accident-notification' import { AccidentNotificationAttachmentStatus } from '../types/applicationStatus' @@ -17,6 +22,9 @@ import { powerOfAttorneyRequest, additionalFilesFromReviewerRequest, } from '../config' +import { AccidentNotificationAttachmentProvider } from './applicationAttachmentProvider' +import { getApplicationDocumentId } from '../accident-notification.utils' +import { utils } from '@island.is/application/templates/accident-notification' export const attachmentStatusToAttachmentRequests = ( receivedAttachments?: AccidentNotificationAttachmentStatus, @@ -91,8 +99,46 @@ export const getAddAttachmentSentDocumentHashList = ( ): string[] => { const addAttachmentAppData = application.externalData .addAdditionalAttachment as ReviewAddAttachmentData - const addAttachmentSentDocuments = - addAttachmentAppData?.data?.sentDocuments ?? [] + return addAttachmentAppData?.data?.sentDocuments ?? [] +} + +export const getReportId = (application: ApplicationWithAttachments) => { + const externalData = application.externalData + return getValueViaPath( + externalData, + 'submitApplication.data.documentId', + ) as number +} + +export const getNewAttachments = async ( + application: ApplicationWithAttachments, + attachmentProvider: AccidentNotificationAttachmentProvider, +) => { + const attachmentStatus = getApplicationAttachmentStatus(application) + const requests = attachmentStatusToAttachmentRequests(attachmentStatus) + const attachments = await attachmentProvider.getFiles(requests, application) - return addAttachmentSentDocuments.map((x) => x) + const newAttachments = filterOutAlreadySentDocuments(attachments, application) + + return newAttachments +} + +export const getReviewApplicationData = (application: Application) => { + const documentId = getApplicationDocumentId(application) + + const isRepresentativeOfCompanyOrInstitute = + utils.isRepresentativeOfCompanyOrInstitute(application.answers) + const reviewApproval = getValueViaPath( + application.answers, + 'reviewApproval', + ) as ReviewApprovalEnum + const reviewComment = + (getValueViaPath(application.answers, 'reviewComment') as string) || '' + + return { + documentId, + isRepresentativeOfCompanyOrInstitute, + reviewApproval, + reviewComment, + } } diff --git a/libs/application/templates/accident-notification/src/fields/ApplicationStatus/applicationStatusUtils.ts b/libs/application/templates/accident-notification/src/fields/ApplicationStatus/applicationStatusUtils.ts new file mode 100644 index 000000000000..a74efdac944b --- /dev/null +++ b/libs/application/templates/accident-notification/src/fields/ApplicationStatus/applicationStatusUtils.ts @@ -0,0 +1,169 @@ +import { FormatMessage, FormValue } from '@island.is/application/types' +import { inReview } from '../../lib/messages' +import { AccidentNotificationStatusEnum, Steps } from './StatusStep/types' +import { getValueViaPath } from '@island.is/application/core' +import { ReviewApprovalEnum } from '../../types' +import { AccidentNotificationStatus } from '@island.is/api/schema' +import { + hasReceivedAllDocuments, + isInjuredAndRepresentativeOfCompanyOrInstitute, + shouldRequestReview, +} from '../../utils' +import { hasReceivedConfirmation } from '../../utils/hasReceivedConfirmation' +import { AccidentNotificationAnswers } from '../..' + +export const tagMapperApplicationStatus = { + [AccidentNotificationStatusEnum.ACCEPTED]: { + variant: 'blue', + text: inReview.tags.received, + }, + [AccidentNotificationStatusEnum.REFUSED]: { + variant: 'blue', + text: inReview.tags.received, + }, + [AccidentNotificationStatusEnum.INPROGRESS]: { + variant: 'purple', + text: inReview.tags.pending, + }, + [AccidentNotificationStatusEnum.INPROGRESSWAITINGFORDOCUMENT]: { + variant: 'purple', + text: inReview.tags.pending, + }, +} + +export const getStatusAndApproval = (answers: FormValue) => { + const currentAccidentStatus = getValueViaPath( + answers, + 'accidentStatus', + ) as AccidentNotificationStatus + + const reviewApproval = getValueViaPath( + answers, + 'reviewApproval', + ReviewApprovalEnum.NOTREVIEWED, + ) as ReviewApprovalEnum + + return { currentAccidentStatus, reviewApproval } +} + +export const firstStep = (formatMessage: FormatMessage) => { + return { + tagText: formatMessage(inReview.tags.received), + tagVariant: 'blue', + title: formatMessage(inReview.application.title), + description: formatMessage(inReview.application.summary), + hasActionMessage: false, + } +} + +export const secondStep = ( + formatMessage: FormatMessage, + answers: FormValue, + errorMessage: string, + changeScreens: (screen: string) => void, +) => { + return { + tagText: hasReceivedAllDocuments(answers) + ? formatMessage(inReview.tags.received) + : formatMessage(inReview.tags.missing), + tagVariant: hasReceivedAllDocuments(answers) ? 'blue' : 'rose', + title: formatMessage(inReview.documents.title), + description: formatMessage(inReview.documents.summary), + hasActionMessage: errorMessage.length > 0, + action: { + cta: () => { + changeScreens('addAttachmentScreen') + }, + title: formatMessage(inReview.action.documents.title), + description: formatMessage(inReview.action.documents.description), + fileNames: errorMessage, // We need to get this from first form + actionButtonTitle: formatMessage( + inReview.action.documents.actionButtonTitle, + ), + hasActionButtonIcon: true, + showAlways: true, + }, + } +} + +export const thirdStep = ( + formatMessage: FormatMessage, + answers: FormValue, + isAssignee: boolean, + changeScreens: (screen: string) => void, +) => { + const hasReviewerSubmitted = hasReceivedConfirmation(answers) + + return { + tagText: hasReviewerSubmitted + ? formatMessage(inReview.tags.received) + : formatMessage(inReview.tags.missing), + tagVariant: hasReviewerSubmitted ? 'blue' : 'rose', + title: formatMessage( + hasReviewerSubmitted + ? inReview.representative.titleDone + : inReview.representative.title, + ), + description: formatMessage( + hasReviewerSubmitted + ? inReview.representative.summaryDone + : inReview.representative.summary, + ), + hasActionMessage: isAssignee && !hasReviewerSubmitted, + action: + isAssignee && !hasReviewerSubmitted + ? { + cta: () => changeScreens('inReviewOverviewScreen'), + title: formatMessage(inReview.action.representative.title), + description: formatMessage( + inReview.action.representative.description, + ), + actionButtonTitle: formatMessage( + inReview.action.representative.actionButtonTitle, + ), + } + : undefined, + visible: !( + !shouldRequestReview(answers as AccidentNotificationAnswers) || + isInjuredAndRepresentativeOfCompanyOrInstitute(answers) + ), + } +} + +export const fourthStep = ( + formatMessage: FormatMessage, + answers: FormValue, + currentAccidentStatus: AccidentNotificationStatus, +) => { + const hasReviewerSubmitted = hasReceivedConfirmation(answers) + + return { + tagText: formatMessage( + tagMapperApplicationStatus[currentAccidentStatus.status].text, + ), + tagVariant: + tagMapperApplicationStatus[currentAccidentStatus.status].variant, + title: formatMessage(inReview.sjukratrygging.title), + description: + hasReviewerSubmitted && hasReceivedAllDocuments(answers) + ? formatMessage(inReview.sjukratrygging.summaryDone) + : formatMessage(inReview.sjukratrygging.summary), + hasActionMessage: false, + } +} + +export const getSteps = ( + formatMessage: FormatMessage, + answers: FormValue, + changeScreens: (screen: string) => void, + errorMessage: string, + isAssignee: boolean, + currentAccidentStatus: AccidentNotificationStatus, +) => { + return [ + firstStep(formatMessage), + secondStep(formatMessage, answers, errorMessage, changeScreens), + thirdStep(formatMessage, answers, isAssignee, changeScreens), + fourthStep(formatMessage, answers, currentAccidentStatus), + ] as Steps[] +} diff --git a/libs/application/templates/accident-notification/src/fields/ApplicationStatus/index.tsx b/libs/application/templates/accident-notification/src/fields/ApplicationStatus/index.tsx index 1ecd50a0085d..0055324cd571 100644 --- a/libs/application/templates/accident-notification/src/fields/ApplicationStatus/index.tsx +++ b/libs/application/templates/accident-notification/src/fields/ApplicationStatus/index.tsx @@ -1,6 +1,5 @@ import { useMutation, useQuery } from '@apollo/client' import { AccidentNotificationStatus } from '@island.is/api/schema' -import { getValueViaPath } from '@island.is/application/core' import { FieldBaseProps, FormValue } from '@island.is/application/types' import { UPDATE_APPLICATION } from '@island.is/application/graphql' import { @@ -11,30 +10,25 @@ import { Text, } from '@island.is/island-ui/core' import { useLocale } from '@island.is/localization' -import React, { FC, useCallback, useEffect } from 'react' +import { useCallback, useEffect } from 'react' import { useFormContext } from 'react-hook-form' -import { AccidentNotificationAnswers } from '../..' import { getAccidentStatusQuery } from '../../hooks/useLazyStatusOfNotification' import { inReview } from '../../lib/messages' import { ReviewApprovalEnum, SubmittedApplicationData } from '../../types' import { getErrorMessageForMissingDocuments, - hasReceivedAllDocuments, - isInjuredAndRepresentativeOfCompanyOrInstitute, - shouldRequestReview, isUniqueAssignee, } from '../../utils' -import { hasReceivedConfirmation } from '../../utils/hasReceivedConfirmation' import { StatusStep } from './StatusStep' -import { - AccidentNotificationStatusEnum, - ApplicationStatusProps, - Steps, -} from './StatusStep/types' - -export const ApplicationStatus: FC< - React.PropsWithChildren -> = ({ goToScreen, application, refetch, field }) => { +import { ApplicationStatusProps } from './StatusStep/types' +import { getStatusAndApproval, getSteps } from './applicationStatusUtils' + +export const ApplicationStatus = ({ + goToScreen, + application, + refetch, + field, +}: ApplicationStatusProps & FieldBaseProps) => { const isAssignee = field?.props?.isAssignee || false const subAppData = application.externalData .submitApplication as SubmittedApplicationData @@ -68,16 +62,8 @@ export const ApplicationStatus: FC< if (goToScreen) goToScreen(screen) } - const currentAccidentStatus = getValueViaPath( - answers, - 'accidentStatus', - ) as AccidentNotificationStatus - - const reviewApproval = getValueViaPath( - answers, - 'reviewApproval', - ReviewApprovalEnum.NOTREVIEWED, - ) as ReviewApprovalEnum + const { currentAccidentStatus, reviewApproval } = + getStatusAndApproval(answers) const hasAccidentStatusChanged = useCallback( ( @@ -163,7 +149,7 @@ export const ApplicationStatus: FC< } }, [data, assignValueToAnswersAndRefetch]) - if (loadingData || loading || !currentAccidentStatus) { + if (loadingData || loading) { return ( <> @@ -173,113 +159,20 @@ export const ApplicationStatus: FC< } // Todo add sentry log and design - if (error) { + if (error || !currentAccidentStatus) { return ( Ekki tókst að sækja stöðu umsóknar, eitthvað fór úrskeiðis. ) } - const tagMapperApplicationStatus = { - [AccidentNotificationStatusEnum.ACCEPTED]: { - variant: 'blue', - text: inReview.tags.received, - }, - [AccidentNotificationStatusEnum.REFUSED]: { - variant: 'blue', - text: inReview.tags.received, - }, - [AccidentNotificationStatusEnum.INPROGRESS]: { - variant: 'purple', - text: inReview.tags.pending, - }, - [AccidentNotificationStatusEnum.INPROGRESSWAITINGFORDOCUMENT]: { - variant: 'purple', - text: inReview.tags.pending, - }, - } - - const hasReviewerSubmitted = hasReceivedConfirmation(answers) - - const steps = [ - { - tagText: formatMessage(inReview.tags.received), - tagVariant: 'blue', - title: formatMessage(inReview.application.title), - description: formatMessage(inReview.application.summary), - hasActionMessage: false, - }, - { - tagText: hasReceivedAllDocuments(answers) - ? formatMessage(inReview.tags.received) - : formatMessage(inReview.tags.missing), - tagVariant: hasReceivedAllDocuments(answers) ? 'blue' : 'rose', - title: formatMessage(inReview.documents.title), - description: formatMessage(inReview.documents.summary), - hasActionMessage: errorMessage.length > 0, - action: { - cta: () => { - changeScreens('addAttachmentScreen') - }, - title: formatMessage(inReview.action.documents.title), - description: formatMessage(inReview.action.documents.description), - fileNames: errorMessage, // We need to get this from first form - actionButtonTitle: formatMessage( - inReview.action.documents.actionButtonTitle, - ), - hasActionButtonIcon: true, - showAlways: true, - }, - }, - // If this was a home activity accident than we don't want the user to see this step - { - tagText: hasReviewerSubmitted - ? formatMessage(inReview.tags.received) - : formatMessage(inReview.tags.missing), - tagVariant: hasReviewerSubmitted ? 'blue' : 'rose', - title: formatMessage( - hasReviewerSubmitted - ? inReview.representative.titleDone - : inReview.representative.title, - ), - description: formatMessage( - hasReviewerSubmitted - ? inReview.representative.summaryDone - : inReview.representative.summary, - ), - hasActionMessage: isAssignee && !hasReviewerSubmitted, - action: - isAssignee && !hasReviewerSubmitted - ? { - cta: () => changeScreens('inReviewOverviewScreen'), - title: formatMessage(inReview.action.representative.title), - description: formatMessage( - inReview.action.representative.description, - ), - actionButtonTitle: formatMessage( - inReview.action.representative.actionButtonTitle, - ), - } - : undefined, - visible: !( - !shouldRequestReview( - application.answers as AccidentNotificationAnswers, - ) || isInjuredAndRepresentativeOfCompanyOrInstitute(application.answers) - ), - }, - { - tagText: formatMessage( - tagMapperApplicationStatus[currentAccidentStatus.status].text, - ), - tagVariant: - tagMapperApplicationStatus[currentAccidentStatus.status].variant, - title: formatMessage(inReview.sjukratrygging.title), - description: - hasReviewerSubmitted && hasReceivedAllDocuments(answers) - ? formatMessage(inReview.sjukratrygging.summaryDone) - : formatMessage(inReview.sjukratrygging.summary), - hasActionMessage: false, - }, - ] as Steps[] + const steps = getSteps( + formatMessage, + answers, + changeScreens, + errorMessage, + isAssignee, + currentAccidentStatus, + ) return ( diff --git a/libs/application/templates/accident-notification/src/fields/FormOverview/index.tsx b/libs/application/templates/accident-notification/src/fields/FormOverview/index.tsx index c1d846e51ca7..e3401b90871a 100644 --- a/libs/application/templates/accident-notification/src/fields/FormOverview/index.tsx +++ b/libs/application/templates/accident-notification/src/fields/FormOverview/index.tsx @@ -408,7 +408,7 @@ export const FormOverview: FC< )} - {answers.isRepresentativeOfCompanyOrInstitute?.toString() !== YES && ( + {answers.isRepresentativeOfCompanyOrInstitue?.toString() !== YES && ( <> {formatText( diff --git a/libs/application/templates/accident-notification/src/forms/InReviewForm/index.ts b/libs/application/templates/accident-notification/src/forms/InReviewForm/index.ts index 8357ccdb6f64..371206d7ae96 100644 --- a/libs/application/templates/accident-notification/src/forms/InReviewForm/index.ts +++ b/libs/application/templates/accident-notification/src/forms/InReviewForm/index.ts @@ -16,6 +16,7 @@ export const ApplicantReview: Form = buildForm({ inReviewOverviewSection(), ], }) + export const AssigneeReview: Form = buildForm({ id: 'assigneeReviewForm', title: inReview.general.formTitle, diff --git a/libs/application/templates/accident-notification/src/index.ts b/libs/application/templates/accident-notification/src/index.ts index c5ea131a219f..512077174291 100644 --- a/libs/application/templates/accident-notification/src/index.ts +++ b/libs/application/templates/accident-notification/src/index.ts @@ -1,6 +1,7 @@ import AccidentNotificationTemplate from './lib/AccidentNotificationTemplate' -import { AccidentNotification, OnBehalf } from './lib/dataSchema' +import { AccidentNotification } from './lib/dataSchema' import * as appMessages from './lib/messages' +import { OnBehalf } from './types' import * as appUtils from './utils' export const getFields = () => import('./fields') diff --git a/libs/application/templates/accident-notification/src/lib/AccidentNotificationTemplate.ts b/libs/application/templates/accident-notification/src/lib/AccidentNotificationTemplate.ts index 6493795fdb1b..064f8b9d2530 100644 --- a/libs/application/templates/accident-notification/src/lib/AccidentNotificationTemplate.ts +++ b/libs/application/templates/accident-notification/src/lib/AccidentNotificationTemplate.ts @@ -25,7 +25,6 @@ import { ApiActions } from '../shared' import { WhoIsTheNotificationForEnum } from '../types' import { AccidentNotificationSchema } from './dataSchema' import { anPendingActionMessages, application } from './messages' -import { Features } from '@island.is/feature-flags' // The applicant is the applicant of the application, can be someone in power of attorney or the representative for the company // The assignee is the person who is assigned to review the application can be the injured person or the representative for the company diff --git a/libs/application/templates/accident-notification/src/lib/dataSchema.ts b/libs/application/templates/accident-notification/src/lib/dataSchema.ts index 3cbf71a55127..a9ee0e7b182b 100644 --- a/libs/application/templates/accident-notification/src/lib/dataSchema.ts +++ b/libs/application/templates/accident-notification/src/lib/dataSchema.ts @@ -1,7 +1,7 @@ import { applicantInformationSchema } from '@island.is/application/ui-forms' import * as kennitala from 'kennitala' import { z } from 'zod' -import { NO, YES } from '../constants' +import { YES } from '../constants' import { AccidentTypeEnum, AgricultureAccidentLocationEnum, @@ -17,15 +17,12 @@ import { WhoIsTheNotificationForEnum, WorkAccidentTypeEnum, ReviewApprovalEnum, + OnBehalf, + Status, + ChoiceEnum, } from '../types' import { isValid24HFormatTime } from '../utils' import { error } from './messages/error' -import { time } from 'console' - -export enum OnBehalf { - MYSELF = 'myself', - OTHERS = 'others', -} const FileSchema = z.object({ name: z.string(), @@ -33,7 +30,7 @@ const FileSchema = z.object({ url: z.string().optional(), }) -const RepresentativeInfo = z.object({ +const representative = z.object({ name: z.string().refine((x) => x.trim().length > 0, { params: error.invalidValue, }), @@ -44,7 +41,7 @@ const RepresentativeInfo = z.object({ phoneNumber: z.string().optional(), }) -const CompanyInfo = z +const companyInfo = z .object({ name: z.string().refine((x) => x.trim().length > 0, { params: error.invalidValue, @@ -57,52 +54,150 @@ const CompanyInfo = z }) .optional() -export const AccidentNotificationSchema = z.object({ - approveExternalData: z.boolean().refine((v) => v), - representative: RepresentativeInfo, - companyInfo: CompanyInfo, - externalData: z.object({ - nationalRegistry: z.object({ - data: z.object({ - address: z.object({ - locality: z.string(), - municipalityCode: z.string(), - postalCode: z.string().refine((x) => +x >= 100 && +x <= 999, { - params: error.invalidValue, - }), - streetAddress: z.string(), - }), - age: z.number(), - citizenship: z.object({ - code: z.string(), - name: z.string(), +const externalData = z.object({ + nationalRegistry: z.object({ + data: z.object({ + address: z.object({ + locality: z.string(), + municipalityCode: z.string(), + postalCode: z.string().refine((x) => +x >= 100 && +x <= 999, { + params: error.invalidValue, }), - fullName: z.string(), - nationalId: z.string(), + streetAddress: z.string(), }), - date: z.string(), - status: z.enum(['success', 'failure']), - }), - userProfile: z.object({ - data: z.object({ - email: z.string(), - mobilePhoneNumber: z.string(), + age: z.number(), + citizenship: z.object({ + code: z.string(), + name: z.string(), }), + fullName: z.string(), + nationalId: z.string(), + }), + date: z.string(), + status: z.nativeEnum(Status), + }), + userProfile: z.object({ + data: z.object({ + email: z.string(), + mobilePhoneNumber: z.string(), + }), + }), +}) + +const attachments = z.object({ + injuryCertificateFile: z.object({ file: z.array(FileSchema) }).optional(), + policeReportFile: z.object({ file: z.array(FileSchema) }).optional(), + deathCertificateFile: z.object({ file: z.array(FileSchema) }).optional(), + powerOfAttorneyFile: z.object({ file: z.array(FileSchema) }).optional(), + additionalFiles: z.object({ file: z.array(FileSchema) }).optional(), + additionalFilesFromReviewer: z + .object({ file: z.array(FileSchema) }) + .optional(), +}) + +const accidentDetails = z.object({ + dateOfAccident: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + isHealthInsured: z.nativeEnum(ChoiceEnum).optional(), + timeOfAccident: z + .string() + .refine((x) => (x ? isValid24HFormatTime(x) : false), { + params: error.invalidValue, + }), + descriptionOfAccident: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + accidentSymptoms: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + dateOfDoctorVisit: z.string().optional(), + timeOfDoctorVisit: z + .string() + .optional() + .refine((x) => (x ? isValid24HFormatTime(x) : true)), +}) + +const fishingShipInfo = z.object({ + shipName: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + shipCharacters: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + homePort: z.string(), + shipRegisterNumber: z.string(), +}) + +const homeAccident = z.object({ + address: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + postalCode: z.string().refine((x) => +x >= 100 && +x <= 999, { + params: error.invalidValue, + }), + community: z + .string() + .regex(/^([^0-9]*)$/) + .refine((x) => x.trim().length > 0, { + params: error.invalidValue, }), + moreDetails: z.string().optional(), +}) + +const injuredPersonInformation = z.object({ + name: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + nationalId: z.string().refine((x) => (x ? kennitala.isPerson(x) : false), { + params: error.invalidValue, }), + email: z + .string() + .email() + .refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + phoneNumber: z.string().optional(), + jobTitle: z.string().optional(), +}) + +const juridicalPerson = z.object({ + companyName: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + companyNationalId: z + .string() + .refine((x) => (x ? kennitala.isCompany(x) : false), { + params: error.invalidValue, + }), + companyConfirmation: z.array(z.string()).refine((v) => v.includes(YES), { + params: error.requiredCheckmark, + }), +}) + +const childInCustody = z.object({ + name: z.string().refine((x) => x.trim().length > 0, { + params: error.invalidValue, + }), + nationalId: z.string().refine((x) => (x ? kennitala.isPerson(x) : false), { + params: error.invalidValue, + }), +}) + +export const AccidentNotificationSchema = z.object({ + approveExternalData: z.boolean().refine((v) => v), + representative, + companyInfo, + externalData, info: z.object({ - onBehalf: z.enum([OnBehalf.MYSELF, OnBehalf.OTHERS]), + onBehalf: z.nativeEnum(OnBehalf), }), - timePassedHindrance: z.enum([YES, NO]), - carAccidentHindrance: z.enum([YES, NO]), + timePassedHindrance: z.nativeEnum(ChoiceEnum), + carAccidentHindrance: z.nativeEnum(ChoiceEnum), applicant: applicantInformationSchema(), whoIsTheNotificationFor: z.object({ - answer: z.enum([ - WhoIsTheNotificationForEnum.JURIDICALPERSON, - WhoIsTheNotificationForEnum.ME, - WhoIsTheNotificationForEnum.POWEROFATTORNEY, - WhoIsTheNotificationForEnum.CHILDINCUSTODY, - ]), + answer: z.nativeEnum(WhoIsTheNotificationForEnum), }), injuryCertificate: z.object({ answer: z.enum([ @@ -117,76 +212,14 @@ export const AccidentNotificationSchema = z.object({ AttachmentsEnum.ADDITIONALLATER, ]), }), - - attachments: z.object({ - injuryCertificateFile: z - .object({ - file: z.array(FileSchema), - // .refine((v) => v.length > 0, { params: error.requiredFile }), - }) - .optional(), - deathCertificateFile: z - .object({ - file: z.array(FileSchema), - // .refine((v) => v.length > 0, { params: error.requiredFile }), - }) - .optional(), - powerOfAttorneyFile: z - .object({ - file: z.array(FileSchema), - // .refine((v) => v.length > 0, { params: error.requiredFile }), - }) - .optional(), - additionalFiles: z - .object({ - file: z.array(FileSchema), - // .refine((v) => v.length > 0, { params: error.requiredFile }), - }) - .optional(), - additionalFilesFromReviewer: z - .object({ - file: z.array(FileSchema), - }) - .optional(), - }), - wasTheAccidentFatal: z.enum([YES, NO]), - fatalAccidentUploadDeathCertificateNow: z.enum([YES, NO]), - accidentDetails: z.object({ - dateOfAccident: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - isHealthInsured: z.enum([YES, NO]).optional(), - timeOfAccident: z - .string() - .refine((x) => (x ? isValid24HFormatTime(x) : false), { - params: error.invalidValue, - }), - descriptionOfAccident: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - accidentSymptoms: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - dateOfDoctorVisit: z.string().optional(), - timeOfDoctorVisit: z - .string() - .optional() - .refine((x) => (x ? isValid24HFormatTime(x) : true)), - }), - isRepresentativeOfCompanyOrInstitute: z.array(z.string()).optional(), - fishingShipInfo: z.object({ - shipName: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - shipCharacters: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - homePort: z.string(), - shipRegisterNumber: z.string(), - }), - + attachments, + wasTheAccidentFatal: z.nativeEnum(ChoiceEnum), + fatalAccidentUploadDeathCertificateNow: z.nativeEnum(ChoiceEnum), + accidentDetails, + isRepresentativeOfCompanyOrInstitue: z.array(z.string()).optional(), + fishingShipInfo, onPayRoll: z.object({ - answer: z.enum([YES, NO]), + answer: z.nativeEnum(ChoiceEnum), }), locationAndPurpose: z.object({ location: z.string().refine((x) => x.trim().length > 0, { @@ -194,130 +227,44 @@ export const AccidentNotificationSchema = z.object({ }), }), accidentLocation: z.object({ - answer: z.enum([ - GeneralWorkplaceAccidentLocationEnum.ATTHEWORKPLACE, - GeneralWorkplaceAccidentLocationEnum.TOORFROMTHEWORKPLACE, - GeneralWorkplaceAccidentLocationEnum.OTHER, - FishermanWorkplaceAccidentLocationEnum.ONTHESHIP, - FishermanWorkplaceAccidentLocationEnum.TOORFROMTHEWORKPLACE, - FishermanWorkplaceAccidentLocationEnum.OTHER, - ProfessionalAthleteAccidentLocationEnum.SPORTCLUBSFACILITES, - ProfessionalAthleteAccidentLocationEnum.TOORFROMTHESPORTCLUBSFACILITES, - ProfessionalAthleteAccidentLocationEnum.OTHER, - AgricultureAccidentLocationEnum.ATTHEWORKPLACE, - AgricultureAccidentLocationEnum.TOORFROMTHEWORKPLACE, - AgricultureAccidentLocationEnum.OTHER, - RescueWorkAccidentLocationEnum.TOORFROMRESCUE, - RescueWorkAccidentLocationEnum.DURINGRESCUE, - RescueWorkAccidentLocationEnum.OTHER, - StudiesAccidentLocationEnum.ATTHESCHOOL, - StudiesAccidentLocationEnum.OTHER, + answer: z.union([ + z.nativeEnum(GeneralWorkplaceAccidentLocationEnum), + z.nativeEnum(FishermanWorkplaceAccidentLocationEnum), + z.nativeEnum(ProfessionalAthleteAccidentLocationEnum), + z.nativeEnum(AgricultureAccidentLocationEnum), + z.nativeEnum(RescueWorkAccidentLocationEnum), + z.nativeEnum(StudiesAccidentLocationEnum), ]), }), - homeAccident: z.object({ - address: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - postalCode: z.string().refine((x) => +x >= 100 && +x <= 999, { - params: error.invalidValue, - }), - community: z - .string() - .regex(/^([^0-9]*)$/) - .refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - moreDetails: z.string().optional(), - }), + homeAccident, shipLocation: z.object({ - answer: z.enum([ - FishermanWorkplaceAccidentShipLocationEnum.SAILINGORFISHING, - FishermanWorkplaceAccidentShipLocationEnum.HARBOR, - FishermanWorkplaceAccidentShipLocationEnum.OTHER, - ]), + answer: z.nativeEnum(FishermanWorkplaceAccidentShipLocationEnum), }), - workMachineRadio: z.enum([YES, NO]), + workMachineRadio: z.nativeEnum(ChoiceEnum), workMachine: z.object({ descriptionOfMachine: z.string().refine((x) => x.trim().length > 0, { params: error.invalidValue, }), }), accidentType: z.object({ - radioButton: z.enum([ - AccidentTypeEnum.WORK, - AccidentTypeEnum.HOMEACTIVITIES, - AccidentTypeEnum.RESCUEWORK, - AccidentTypeEnum.SPORTS, - AccidentTypeEnum.STUDIES, - ]), + radioButton: z.nativeEnum(AccidentTypeEnum), }), workAccident: z.object({ - type: z.enum([ - WorkAccidentTypeEnum.AGRICULTURE, - WorkAccidentTypeEnum.FISHERMAN, - WorkAccidentTypeEnum.GENERAL, - WorkAccidentTypeEnum.PROFESSIONALATHLETE, - ]), + type: z.nativeEnum(WorkAccidentTypeEnum), jobTitle: z.string().optional(), }), studiesAccident: z.object({ - type: z.enum([ - StudiesAccidentTypeEnum.APPRENTICESHIP, - StudiesAccidentTypeEnum.INTERNSHIP, - StudiesAccidentTypeEnum.VOCATIONALEDUCATION, - ]), - }), - injuredPersonInformation: z.object({ - name: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - nationalId: z.string().refine((x) => (x ? kennitala.isPerson(x) : false), { - params: error.invalidValue, - }), - email: z - .string() - .email() - .refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - phoneNumber: z.string().optional(), - jobTitle: z.string().optional(), - }), - juridicalPerson: z.object({ - companyName: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - companyNationalId: z - .string() - .refine((x) => (x ? kennitala.isCompany(x) : false), { - params: error.invalidValue, - }), - companyConfirmation: z.array(z.string()).refine((v) => v.includes(YES), { - params: error.requiredCheckmark, - }), - }), - childInCustody: z.object({ - name: z.string().refine((x) => x.trim().length > 0, { - params: error.invalidValue, - }), - nationalId: z.string().refine((x) => (x ? kennitala.isPerson(x) : false), { - params: error.invalidValue, - }), + type: z.nativeEnum(StudiesAccidentTypeEnum), }), + injuredPersonInformation, + juridicalPerson, + childInCustody, powerOfAttorney: z.object({ - type: z.enum([ - PowerOfAttorneyUploadEnum.FORCHILDINCUSTODY, - PowerOfAttorneyUploadEnum.UPLOADLATER, - PowerOfAttorneyUploadEnum.UPLOADNOW, - ]), + type: z.nativeEnum(PowerOfAttorneyUploadEnum), }), reviewApproval: z - .enum([ - ReviewApprovalEnum.APPROVED, - ReviewApprovalEnum.REJECTED, - ReviewApprovalEnum.NOTREVIEWED, - ]) - .refine((x) => (x ? x : ReviewApprovalEnum.NOTREVIEWED)), + .nativeEnum(ReviewApprovalEnum) + .refine((x) => x ?? ReviewApprovalEnum.NOTREVIEWED), reviewComment: z.string().optional(), }) diff --git a/libs/application/templates/accident-notification/src/types/index.ts b/libs/application/templates/accident-notification/src/types/index.ts index 06bcf2d5b662..1b3694a9b695 100644 --- a/libs/application/templates/accident-notification/src/types/index.ts +++ b/libs/application/templates/accident-notification/src/types/index.ts @@ -3,8 +3,6 @@ import { NO, YES } from './../constants' export type CompanyInfo = { nationalRegistrationId: string name: string - // email: string - // phoneNumber: string type: AccidentTypeEnum | WorkAccidentTypeEnum onPayRoll?: { answer: YesOrNo @@ -16,6 +14,12 @@ export type Applicant = { nationalId: string email: string phoneNumber: string + jobTitle?: string +} + +export enum Status { + SUCCESS = 'success', + FAILURE = 'failure', } export type AccidentNotifTypes = @@ -44,6 +48,16 @@ export type FileType = { key: string } +export enum OnBehalf { + MYSELF = 'myself', + OTHERS = 'others', +} + +export enum ChoiceEnum { + YES = 'yes', + NO = 'no', +} + export enum DataProviderTypes { NationalRegistry = 'NationalRegistryProvider', UserProfile = 'UserProfileProvider', @@ -161,3 +175,62 @@ export interface ReviewAddAttachmentData { sentDocuments: string[] } } + +// Types for new accident notification API + +export type ApplicantV2 = { + address?: string | null + city?: string | null + email?: string | null + name?: string | null + nationalId?: string | null + phoneNumber?: string | null + postalCode?: string | null +} + +export type InjuredPersonInformationV2 = { + email?: string | null + jobTitle?: string | null + name?: string | null + nationalId?: string | null + phoneNumber?: string | null +} + +export type AccidentDetailsV2 = { + accidentSymptoms?: string | null + dateOfAccident?: string | null + dateOfDoctorVisit?: string | null + descriptionOfAccident?: string | null + timeOfAccident?: string | null + timeOfDoctorVisit?: string | null +} + +export type HomeAccidentV2 = { + address?: string | null + community?: string | null + moreDetails?: string | null + postalCode?: string | null +} + +export type WorkMachineV2 = { + descriptionOfMachine?: string | null +} + +export type FishingShipInfoV2 = { + homePort?: string | null + shipCharacters?: string | null + shipName?: string | null + shipRegisterNumber?: string | null +} + +export type CompanyInfoV2 = { + name?: string | null + nationalRegistrationId?: string | null +} + +export type RepresentativeInfoV2 = { + email?: string | null + name?: string | null + nationalId?: string | null + phoneNumber?: string | null +} diff --git a/libs/application/ui-shell/src/lib/FormShell.tsx b/libs/application/ui-shell/src/lib/FormShell.tsx index 51803e189f7a..ab9ad39d8146 100644 --- a/libs/application/ui-shell/src/lib/FormShell.tsx +++ b/libs/application/ui-shell/src/lib/FormShell.tsx @@ -101,37 +101,6 @@ export const FormShell: FC< return } - const parseSubsections = ( - children: Array, - isParentActive: boolean, - ) => { - const childrenToParse: Array = [] - - children.forEach((child) => { - const childScreen = screens.find((s) => s.id === child.id) - - if (childScreen?.subSectionIndex === -1) { - return null - } - - childrenToParse.push(child) - }) - - return childrenToParse.map((child, i) => { - const isChildActive = - isParentActive && currentScreen.subSectionIndex === i - return ( - - {formatMessage(child.title as MessageDescriptor)} - - ) - }) - } - return ( ({ provide: Api, useFactory: (configuration: Configuration) => {