diff --git a/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js b/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js index b788e6d93b..3cb50a0814 100644 --- a/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js +++ b/app/cypress/e2e/1-ndr-smoke-tests/gp_user_workflows/upload_lloyd_george_is_bsol_gp_admin_workflow.cy.js @@ -6,14 +6,14 @@ const workspace = Cypress.env('WORKSPACE'); const baseUrl = Cypress.config('baseUrl'); const uploadedFilePathNames = [ - 'cypress/fixtures/lg-files/zenia_lees/1of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', - 'cypress/fixtures/lg-files/zenia_lees/2of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', - 'cypress/fixtures/lg-files/zenia_lees/3of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + 'cypress/fixtures/lg-files/zenia_lees/1of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + 'cypress/fixtures/lg-files/zenia_lees/2of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + 'cypress/fixtures/lg-files/zenia_lees/3of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', ]; const uploadedFileNames = [ - '1of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', - '2of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', - '3of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + '1of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + '2of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', + '3of3_Lloyd_George_Record_[Zenia Ellisa LEES]_[9730153930]_[20-03-1929].pdf', ]; const bucketName = `${workspace}-lloyd-george-store`; @@ -28,108 +28,111 @@ const confirmationUrl = '/patient/document-upload/confirmation'; const activePatient = pdsPatients.activeNoUpload; describe('GP Workflow: Upload Lloyd George record', () => { - context('Upload a Lloyd George document', () => { - beforeEach(() => { - //delete any records present for the active patient - cy.deleteItemsBySecondaryKeyFromDynamoDb( - referenceTableName, - 'NhsNumberIndex', - 'NhsNumber', - activePatient.toString(), - ); - cy.deleteItemsBySecondaryKeyFromDynamoDb( - stitchTableName, - 'NhsNumberIndex', - 'NhsNumber', - activePatient.toString() - ); - uploadedFileNames.forEach((file) => { - cy.deleteFileFromS3(bucketName, file); - }); - }); - - afterEach(() => { - //clean up any records present for the active patient - cy.deleteItemsBySecondaryKeyFromDynamoDb( - referenceTableName, - 'NhsNumberIndex', - 'NhsNumber', - activePatient.toString(), - ); - cy.deleteItemsBySecondaryKeyFromDynamoDb( - stitchTableName, - 'NhsNumberIndex', - 'NhsNumber', - activePatient.toString() - ); - uploadedFileNames.forEach((file) => { - cy.deleteFileFromS3(bucketName, file); - }); - }); - - it( - '[Smoke] GP ADMIN can upload multiple files and then view a Lloyd George record for an active patient with no record', - { tags: 'smoke', defaultCommandTimeout: 20000 }, - () => { - cy.smokeLogin(Roles.SMOKE_GP_ADMIN); - - cy.navigateToPatientSearchPage(); - - cy.get('#nhs-number-input').should('exist'); - cy.get('#nhs-number-input').click(); - cy.get('#nhs-number-input').type(activePatient); - cy.getByTestId('search-submit-btn').should('exist'); - cy.getByTestId('search-submit-btn').click(); - - cy.url({ timeout: 15000 }).should('contain', patientVerifyUrl); - - cy.get('#verify-submit').should('exist'); - cy.get('#verify-submit').click(); - - cy.url().should('contain', lloydGeorgeRecordUrl); - cy.getByTestId('no-records-title').should( - 'include.text', - 'This patient does not have a Lloyd George record', - ); - cy.getByTestId('upload-patient-record-button').should('exist'); - cy.getByTestId('upload-patient-record-button').click(); - uploadedFilePathNames.forEach((file) => { - cy.getByTestId('button-input').selectFile(file, { force: true }); - var index = uploadedFilePathNames.indexOf(file); - cy.get('#selected-documents-table').should('contain', uploadedFileNames[index]); + context('Upload a Lloyd George document', () => { + beforeEach(() => { + //delete any records present for the active patient + cy.deleteItemsBySecondaryKeyFromDynamoDb( + referenceTableName, + 'NhsNumberIndex', + 'NhsNumber', + activePatient.toString(), + ); + cy.deleteItemsBySecondaryKeyFromDynamoDb( + stitchTableName, + 'NhsNumberIndex', + 'NhsNumber', + activePatient.toString(), + ); + uploadedFileNames.forEach((file) => { + cy.deleteFileFromS3(bucketName, file); + }); }); - cy.get('#continue-button').click(); - cy.url().should('contain', selectOrderUrl); - cy.get('#selected-documents-table').should('exist'); - uploadedFileNames.forEach((name) => { - cy.get('#selected-documents-table').should('contain', name); + afterEach(() => { + //clean up any records present for the active patient + cy.deleteItemsBySecondaryKeyFromDynamoDb( + referenceTableName, + 'NhsNumberIndex', + 'NhsNumber', + activePatient.toString(), + ); + cy.deleteItemsBySecondaryKeyFromDynamoDb( + stitchTableName, + 'NhsNumberIndex', + 'NhsNumber', + activePatient.toString(), + ); + uploadedFileNames.forEach((file) => { + cy.deleteFileFromS3(bucketName, file); + }); }); - cy.getByTestId('form-submit-button').click(); - cy.url().should('contain', confirmationUrl); - uploadedFileNames.forEach((name) => { - cy.get('#selected-documents-table').should('contain', name); - }); - cy.getByTestId('confirm-button').click(); - - cy.getByTestId('upload-complete-page', { timeout: 25000 }).should('exist'); - cy.getByTestId('upload-complete-page') - .should('include.text', 'You have successfully uploaded a digital Lloyd George record for'); - - cy.getByTestId('upload-complete-card').should('be.visible'); - - cy.getByTestId('home-btn').eq(1).click(); - - cy.navigateToPatientSearchPage(); - - cy.get('#nhs-number-input').type(activePatient); - cy.get('#search-submit').click(); - cy.wait(5000) - - cy.get('.patient-results-form').submit(); - - cy.get("#pdf-viewer", {timeout: 20000}).should('exist'); - }); - }); + it( + '[Smoke] GP ADMIN can upload multiple files and then view a Lloyd George record for an active patient with no record', + { tags: 'smoke', defaultCommandTimeout: 20000 }, + () => { + cy.smokeLogin(Roles.SMOKE_GP_ADMIN); + + cy.navigateToPatientSearchPage(); + + cy.get('#nhs-number-input').should('exist'); + cy.get('#nhs-number-input').click(); + cy.get('#nhs-number-input').type(activePatient); + cy.getByTestId('search-submit-btn').should('exist'); + cy.getByTestId('search-submit-btn').click(); + + cy.url({ timeout: 15000 }).should('contain', patientVerifyUrl); + + cy.get('#verify-submit').should('exist'); + cy.get('#verify-submit').click(); + + cy.url().should('contain', lloydGeorgeRecordUrl); + cy.getByTestId('no-records-title').should( + 'include.text', + 'This patient does not have a Lloyd George record', + ); + cy.getByTestId('upload-patient-record-button').should('exist'); + cy.getByTestId('upload-patient-record-button').click(); + uploadedFilePathNames.forEach((file) => { + cy.getByTestId('button-input').selectFile(file, { force: true }); + var index = uploadedFilePathNames.indexOf(file); + cy.get('#selected-documents-table').should('contain', uploadedFileNames[index]); + }); + cy.get('#continue-button').click(); + + cy.url().should('contain', selectOrderUrl); + cy.get('#selected-documents-table').should('exist'); + uploadedFileNames.forEach((name) => { + cy.get('#selected-documents-table').should('contain', name); + }); + cy.getByTestId('form-submit-button').click(); + + cy.url().should('contain', confirmationUrl); + uploadedFileNames.forEach((name) => { + cy.get('#selected-documents-table').should('contain', name); + }); + cy.getByTestId('confirm-button').click(); + + cy.getByTestId('upload-complete-page', { timeout: 25000 }).should('exist'); + cy.getByTestId('upload-complete-page').should( + 'include.text', + 'You have successfully uploaded a digital Lloyd George record for', + ); + + cy.getByTestId('upload-complete-card').should('be.visible'); + + cy.getByTestId('home-btn').eq(1).click(); + + cy.navigateToPatientSearchPage(); + + cy.get('#nhs-number-input').type(activePatient); + cy.get('#search-submit').click(); + cy.wait(5000); + + cy.get('.patient-results-form').submit(); + + cy.get('#pdf-viewer', { timeout: 20000 }).should('exist'); + }, + ); + }); }); diff --git a/app/cypress/support/patients.ts b/app/cypress/support/patients.ts index fc03e13079..39e1be9ed2 100644 --- a/app/cypress/support/patients.ts +++ b/app/cypress/support/patients.ts @@ -1,8 +1,8 @@ export const pdsPatients = { - activeUpload: 9730153817, - activeNoUpload: 9730153930, + activeUpload: 9730153817, + activeNoUpload: 9730153930, }; export const stubPatients = { - activeUpload: 9730153817, - activeNoUpload: 9000000068, + activeUpload: 9730153817, + activeNoUpload: 9000000068, }; diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx index 0f2aeb9f6c..ae8de73e2b 100644 --- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx +++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx @@ -42,12 +42,12 @@ const DocumentUploadCompleteStage = ({ documents, documentConfig }: Props): Reac ]); useEffect(() => { - if (!docsAreInFinishedState()) { + if (!docsAreInFinishedState() || patientDetails === null) { navigate(routes.HOME); } - }, [navigate, documents]); + }, [navigate, documents, patientDetails]); - if (!docsAreInFinishedState()) { + if (!docsAreInFinishedState() || patientDetails === null) { return <>; } @@ -108,7 +108,7 @@ const DocumentUploadCompleteStage = ({ documents, documentConfig }: Props): Reac

What happens next

- {journey === 'update' && ( + {journey === 'update' && patientDetails.canManageRecord && (

You can now view the updated {documentConfig.displayName} for this patient in this service by{' '} diff --git a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx index 9f63905e08..b239ae8f9e 100644 --- a/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx +++ b/app/src/components/blocks/_patientDocuments/documentView/DocumentView.tsx @@ -262,14 +262,14 @@ const DocumentView = ({ )} - {documentReference.url - ? getRecordCard() - : ( -

- This document is currently being uploaded, please try again in a few minutes. -

- ) - } + {documentReference.url ? ( + getRecordCard() + ) : ( +

+ This document is currently being uploaded, please try again in a few + minutes. +

+ )} ); diff --git a/app/src/helpers/requests/downloadReport.test.ts b/app/src/helpers/requests/downloadReport.test.ts index f7e23fbd0d..0001848c52 100644 --- a/app/src/helpers/requests/downloadReport.test.ts +++ b/app/src/helpers/requests/downloadReport.test.ts @@ -55,7 +55,7 @@ describe('downloadReport', () => { expect(getSpy).toHaveBeenCalledWith(args.baseUrl + report.endpoint, { headers: args.baseHeaders, - params: { outputFileFormat: args.fileType, odsReportType: "PATIENT" }, + params: { outputFileFormat: args.fileType, odsReportType: 'PATIENT' }, }); expect(mockAnchor.setAttribute).toHaveBeenCalledWith('download', ''); @@ -90,7 +90,7 @@ describe('downloadReport', () => { expect(errorCode).toBe(404); expect(getSpy).toHaveBeenCalledWith(args.baseUrl + report.endpoint, { headers: args.baseHeaders, - params: { outputFileFormat: args.fileType, odsReportType: "PATIENT" }, + params: { outputFileFormat: args.fileType, odsReportType: 'PATIENT' }, }); }); }); diff --git a/app/src/helpers/requests/downloadReport.ts b/app/src/helpers/requests/downloadReport.ts index 0a45f55bfe..25a213e431 100644 --- a/app/src/helpers/requests/downloadReport.ts +++ b/app/src/helpers/requests/downloadReport.ts @@ -23,7 +23,7 @@ const downloadReport = async ({ report, fileType, baseUrl, baseHeaders }: Args): }, params: { outputFileFormat: fileType, - odsReportType: "PATIENT" + odsReportType: 'PATIENT', }, }); diff --git a/app/src/helpers/requests/uploadDocument.test.ts b/app/src/helpers/requests/uploadDocument.test.ts index b764d3c015..ba1d6fbb4f 100644 --- a/app/src/helpers/requests/uploadDocument.test.ts +++ b/app/src/helpers/requests/uploadDocument.test.ts @@ -613,7 +613,10 @@ describe('Upload Document Requests', () => { birthDate: '1990-05-15', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( '1of1_Lloyd_George_Record_[John Michael SMITH]_[1234567890]_[15-05-1990].pdf', @@ -628,9 +631,14 @@ describe('Upload Document Requests', () => { birthDate: '1985-12-25', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); - expect(result).toBe('1of1_Lloyd_George_Record_[Jane DOE]_[0987654321]_[25-12-1985].pdf'); + expect(result).toBe( + '1of1_Lloyd_George_Record_[Jane DOE]_[0987654321]_[25-12-1985].pdf', + ); }); it('handles special characters in given name by replacing them with dashes', () => { @@ -641,7 +649,10 @@ describe('Upload Document Requests', () => { birthDate: '1975-03-10', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( "1of1_Lloyd_George_Record_[Mary-Jane O'Connor SMITH-JONES]_[1111222233]_[10-03-1975].pdf", @@ -656,7 +667,10 @@ describe('Upload Document Requests', () => { birthDate: '2000-01-01', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( '1of1_Lloyd_George_Record_[Test-Name- SAMPLE*FAMILY]_[5555666677]_[01-01-2000].pdf', @@ -671,9 +685,14 @@ describe('Upload Document Requests', () => { birthDate: '1965-07-20', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); - expect(result).toBe('1of1_Lloyd_George_Record_[ ONLYFAMILY]_[9999888877]_[20-07-1965].pdf'); + expect(result).toBe( + '1of1_Lloyd_George_Record_[ ONLYFAMILY]_[9999888877]_[20-07-1965].pdf', + ); }); it('handles birth date with single digit day and month', () => { @@ -684,9 +703,14 @@ describe('Upload Document Requests', () => { birthDate: '1992-02-05', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); - expect(result).toBe('1of1_Lloyd_George_Record_[Alex WILSON]_[1122334455]_[05-02-1992].pdf'); + expect(result).toBe( + '1of1_Lloyd_George_Record_[Alex WILSON]_[1122334455]_[05-02-1992].pdf', + ); }); it('throws an error when patient details is null', () => { @@ -703,7 +727,10 @@ describe('Upload Document Requests', () => { birthDate: '1980-06-15', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( '1of1_Lloyd_George_Record_[Test-Name-With-Various-Characters-With-More---And-Finally- NORMALFAMILY]_[1234567890]_[15-06-1980].pdf', @@ -718,7 +745,10 @@ describe('Upload Document Requests', () => { birthDate: '1995-09-30', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( '1of1_Lloyd_George_Record_[Supercalifragilisticexpialidocious AnExtremelyLongMiddleName ANEXTREMELYLONGFAMILYNAMETHATGOESONANDON]_[1111111111]_[30-09-1995].pdf', @@ -733,9 +763,14 @@ describe('Upload Document Requests', () => { birthDate: 'invalid-date', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); - expect(result).toBe('1of1_Lloyd_George_Record_[Test USER]_[1234567890]_[NaN-NaN-NaN].pdf'); + expect(result).toBe( + '1of1_Lloyd_George_Record_[Test USER]_[1234567890]_[NaN-NaN-NaN].pdf', + ); }); it('handles whitespace in names correctly', () => { @@ -746,7 +781,10 @@ describe('Upload Document Requests', () => { birthDate: '1990-01-01', }); - const result = generateStitchedFileName(patientDetails, docConfig as DOCUMENT_TYPE_CONFIG); + const result = generateStitchedFileName( + patientDetails, + docConfig as DOCUMENT_TYPE_CONFIG, + ); expect(result).toBe( '1of1_Lloyd_George_Record_[ John Michael SMITH ]_[1234567890]_[01-01-1990].pdf', diff --git a/app/src/helpers/requests/uploadDocuments.ts b/app/src/helpers/requests/uploadDocuments.ts index 2d90e811db..8977a4b05c 100644 --- a/app/src/helpers/requests/uploadDocuments.ts +++ b/app/src/helpers/requests/uploadDocuments.ts @@ -3,11 +3,7 @@ import { endpoints } from '../../types/generic/endpoints'; import { DOCUMENT_UPLOAD_STATE, UploadDocument } from '../../types/pages/UploadDocumentsPage/types'; import axios, { AxiosError } from 'axios'; -import { - DocumentStatusResult, - S3Upload, - UploadSession, -} from '../../types/generic/uploadResult'; +import { DocumentStatusResult, S3Upload, UploadSession } from '../../types/generic/uploadResult'; import { Dispatch, SetStateAction } from 'react'; import { extractUploadSession, setSingleDocument } from '../utils/uploadDocumentHelpers'; import { PatientDetails } from '../../types/generic/patientDetails'; diff --git a/app/src/helpers/test/testBuilders.ts b/app/src/helpers/test/testBuilders.ts index bc0f288f76..8e58accd24 100644 --- a/app/src/helpers/test/testBuilders.ts +++ b/app/src/helpers/test/testBuilders.ts @@ -40,6 +40,7 @@ const buildPatientDetails = (patientDetailsOverride?: Partial): restricted: false, active: true, deceased: false, + canManageRecord: true, ...patientDetailsOverride, }; diff --git a/app/src/helpers/utils/documentUpload.test.ts b/app/src/helpers/utils/documentUpload.test.ts index 4518c8dc3f..155486cfd2 100644 --- a/app/src/helpers/utils/documentUpload.test.ts +++ b/app/src/helpers/utils/documentUpload.test.ts @@ -1,12 +1,26 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { reduceDocumentsForUpload } from './documentUpload'; +import { describe, it, expect, vi, beforeEach, Mock } from 'vitest'; +import { + getUploadSession, + handleDocReviewStatusResult, + handleDocStatusResult, + reduceDocumentsForUpload, +} from './documentUpload'; import { PatientDetails } from '../../types/generic/patientDetails'; -import { DOCUMENT_UPLOAD_STATE, UploadDocument } from '../../types/pages/UploadDocumentsPage/types'; +import { + DOCUMENT_STATUS, + DOCUMENT_UPLOAD_STATE, + UploadDocument, +} from '../../types/pages/UploadDocumentsPage/types'; import { DOCUMENT_TYPE, DOCUMENT_TYPE_CONFIG } from './documentType'; -import { generateStitchedFileName } from '../requests/uploadDocuments'; +import uploadDocuments, { generateStitchedFileName } from '../requests/uploadDocuments'; import { zipFiles } from './zip'; +import { buildMockUploadSession } from '../test/testBuilders'; +import { uploadDocumentForReview } from '../requests/documentReview'; +import * as isLocal from './isLocal'; +import { DocumentReviewStatus } from '../../types/blocks/documentReview'; vi.mock('../requests/uploadDocuments'); +vi.mock('../requests/documentReview'); vi.mock('./zip'); vi.mock('uuid', () => ({ v4: vi.fn(() => 'mock-uuid-123'), @@ -49,6 +63,8 @@ describe('documentUpload', () => { const mockMergedPdfBlob = new Blob(['merged pdf content'], { type: 'application/pdf' }); beforeEach(() => { + import.meta.env.VITE_ENVIRONMENT = 'vitest'; + vi.spyOn(isLocal, 'isLocal', 'get').mockReturnValue(false); vi.clearAllMocks(); }); @@ -201,4 +217,435 @@ describe('documentUpload', () => { expect(result[0].file.name).toBe('empty_documents_(0).zip'); }); }); + + describe('getUploadSession', () => { + const baseUrl = 'https://api.example.com'; + const baseHeaders = { Authorization: 'Bearer token', 'Content-Type': 'application/json' }; + const mockSetDocuments = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return mock upload session when isLocal is true', async () => { + vi.spyOn(isLocal, 'isLocal', 'get').mockReturnValueOnce(true); + + const result = await getUploadSession( + mockPatientDetails, + baseUrl, + baseHeaders, + [], + mockDocuments, + mockSetDocuments, + ); + + expect(result).toEqual(buildMockUploadSession(mockDocuments)); + }); + + it('should call uploadDocuments when user can manage patient record', async () => { + const patientWithPermission = { + ...mockPatientDetails, + canManageRecord: true, + }; + + const mockUploadSession = { + doc1: { url: 'presigned-url-1' }, + doc2: { url: 'presigned-url-2' }, + }; + + vi.mocked(uploadDocuments).mockResolvedValue(mockUploadSession); + + const existingDocs = [{ ...mockDocuments[0], id: 'existing-doc-id' }]; + + const result = await getUploadSession( + patientWithPermission, + baseUrl, + baseHeaders, + existingDocs, + mockDocuments, + mockSetDocuments, + ); + + expect(uploadDocuments).toHaveBeenCalledWith({ + nhsNumber: patientWithPermission.nhsNumber, + documents: mockDocuments, + baseUrl, + baseHeaders, + documentReferenceId: 'existing-doc-id', + }); + expect(result).toEqual(mockUploadSession); + }); + + it('should call uploadDocumentForReview when user cannot manage patient record', async () => { + const patientWithoutPermission = { + ...mockPatientDetails, + canManageRecord: false, + }; + + const mockReviewDocs = [ + { + id: 'review-id-1', + version: 'v1', + files: [{ presignedUrl: 'review-url-1' }], + }, + { + id: 'review-id-2', + version: 'v2', + files: [{ presignedUrl: 'review-url-2' }], + }, + ]; + + vi.mocked(uploadDocumentForReview) + .mockResolvedValueOnce(mockReviewDocs[0] as any) + .mockResolvedValueOnce(mockReviewDocs[1] as any); + + const result = await getUploadSession( + patientWithoutPermission, + baseUrl, + baseHeaders, + [], + mockDocuments, + mockSetDocuments, + ); + + expect(uploadDocumentForReview).toHaveBeenCalledTimes(2); + expect(uploadDocumentForReview).toHaveBeenCalledWith({ + nhsNumber: patientWithoutPermission.nhsNumber, + document: mockDocuments[0], + baseUrl, + baseHeaders, + }); + expect(mockSetDocuments).toHaveBeenCalled(); + expect(result).toEqual({ + 'review-id-1': { url: 'review-url-1' }, + 'review-id-2': { url: 'review-url-2' }, + }); + }); + + it('should update document ids and versions when uploading for review', async () => { + const patientWithoutPermission = { + ...mockPatientDetails, + canManageRecord: false, + }; + + const mockReview = { + id: 'new-review-id', + version: 'new-version', + files: [{ presignedUrl: 'new-presigned-url' }], + }; + + vi.mocked(uploadDocumentForReview).mockResolvedValue(mockReview as any); + + await getUploadSession( + patientWithoutPermission, + baseUrl, + baseHeaders, + [], + [mockDocuments[0]], + mockSetDocuments, + ); + + const setDocumentsCall = mockSetDocuments.mock.calls[0][0]; + expect(setDocumentsCall[0].id).toBe('new-review-id'); + expect(setDocumentsCall[0].versionId).toBe('new-version'); + }); + }); + + describe('handleDocStatusResult', () => { + const mockSetDocuments = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should update document state to SUCCEEDED when status is FINAL', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.FINAL as const }, + }; + + const mockDocs: UploadDocument[] = [ + { + ...mockDocuments[0], + ref: 'doc-ref-1', + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.SUCCEEDED); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to INFECTED when status is INFECTED', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.INFECTED as const }, + }; + + const mockDocs: UploadDocument[] = [ + { + ...mockDocuments[0], + ref: 'doc-ref-1', + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.INFECTED); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to ERROR when status is NOT_FOUND', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.NOT_FOUND as const, error_code: 'ERR_404' }, + }; + + const mockDocs: UploadDocument[] = [ + { + ...mockDocuments[0], + ref: 'doc-ref-1', + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.ERROR); + expect(result[0].errorCode).toBe('ERR_404'); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to ERROR when status is CANCELLED', () => { + const documentStatusResult = { + 'doc-ref-1': { + status: DOCUMENT_STATUS.CANCELLED as const, + error_code: 'ERR_CANCELLED', + }, + }; + + const mockDocs: UploadDocument[] = [ + { + ...mockDocuments[0], + ref: 'doc-ref-1', + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.ERROR); + expect(result[0].errorCode).toBe('ERR_CANCELLED'); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should handle multiple documents with different statuses', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.FINAL as const }, + 'doc-ref-2': { status: DOCUMENT_STATUS.INFECTED as const }, + 'doc-ref-3': { status: DOCUMENT_STATUS.NOT_FOUND as const, error_code: 'ERR_404' }, + }; + + const mockDocs: UploadDocument[] = [ + { ...mockDocuments[0], ref: 'doc-ref-1', state: DOCUMENT_UPLOAD_STATE.UPLOADING }, + { ...mockDocuments[1], ref: 'doc-ref-2', state: DOCUMENT_UPLOAD_STATE.UPLOADING }, + { ...mockDocuments[0], ref: 'doc-ref-3', state: DOCUMENT_UPLOAD_STATE.UPLOADING }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.SUCCEEDED); + expect(result[1].state).toBe(DOCUMENT_UPLOAD_STATE.INFECTED); + expect(result[2].state).toBe(DOCUMENT_UPLOAD_STATE.ERROR); + expect(result[2].errorCode).toBe('ERR_404'); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should not modify documents without matching refs', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.FINAL as const }, + }; + + const mockDocs: UploadDocument[] = [ + { ...mockDocuments[0], ref: 'doc-ref-2', state: DOCUMENT_UPLOAD_STATE.UPLOADING }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.UPLOADING); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should preserve other document properties when updating state', () => { + const documentStatusResult = { + 'doc-ref-1': { status: DOCUMENT_STATUS.FINAL as const }, + }; + + const mockDocs: UploadDocument[] = [ + { + ...mockDocuments[0], + ref: 'doc-ref-1', + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + progress: 75, + }, + ]; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn(mockDocs); + expect(result[0].progress).toBe(75); + expect(result[0].docType).toBe(DOCUMENT_TYPE.LLOYD_GEORGE); + expect(result[0].file).toBe(mockDocs[0].file); + }); + + handleDocStatusResult(documentStatusResult, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + }); + + describe('handleDocReviewStatusResult', () => { + const mockSetDocuments = vi.fn(); + + const baseDoc: UploadDocument = { + id: 'doc1', + file: new File(['content1'], 'file1.pdf', { type: 'application/pdf' }), + state: DOCUMENT_UPLOAD_STATE.UPLOADING, + progress: 0, + docType: DOCUMENT_TYPE.LLOYD_GEORGE, + attempts: 0, + versionId: 'v1', + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should update document state to SUCCEEDED when status is PENDING_REVIEW', () => { + const reviewStatusDto = { + id: 'doc1', + status: DocumentReviewStatus.PENDING_REVIEW, + }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([baseDoc]); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.SUCCEEDED); + }); + + // 1 = DocumentReviewStatus.PENDING_REVIEW + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to INFECTED when status is VIRUS_SCAN_FAILED', () => { + const reviewStatusDto = { + id: 'doc1', + status: DocumentReviewStatus.VIRUS_SCAN_FAILED, + }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([baseDoc]); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.INFECTED); + }); + + // 2 = DocumentReviewStatus.VIRUS_SCAN_FAILED + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to SCANNING when status is REVIEW_PENDING_UPLOAD', () => { + const reviewStatusDto = { + id: 'doc1', + status: DocumentReviewStatus.REVIEW_PENDING_UPLOAD, + }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([baseDoc]); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.SCANNING); + }); + + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should update document state to ERROR and set errorCode for unknown status', () => { + const reviewStatusDto = { + id: 'doc1', + status: 'unknown', + reviewReason: 'Some error reason', + }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([baseDoc]); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.ERROR); + expect(result[0].errorCode).toBe('Some error reason'); + }); + + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should not modify documents with non-matching ids', () => { + const reviewStatusDto = { + id: 'other-doc', + status: DocumentReviewStatus.PENDING_REVIEW, + }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([baseDoc]); + expect(result[0].state).toBe(DOCUMENT_UPLOAD_STATE.UPLOADING); + }); + + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + + it('should preserve other document properties when updating state', () => { + const reviewStatusDto = { + id: 'doc1', + status: DocumentReviewStatus.PENDING_REVIEW, + }; + + const docWithProps = { ...baseDoc, progress: 55, attempts: 2 }; + + mockSetDocuments.mockImplementation((updateFn) => { + const result = updateFn([docWithProps]); + expect(result[0].progress).toBe(55); + expect(result[0].attempts).toBe(2); + expect(result[0].file).toBe(docWithProps.file); + }); + + handleDocReviewStatusResult(reviewStatusDto as any, mockSetDocuments); + + expect(mockSetDocuments).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/app/src/helpers/utils/documentUpload.ts b/app/src/helpers/utils/documentUpload.ts index 3ac49132a7..616c98b1f9 100644 --- a/app/src/helpers/utils/documentUpload.ts +++ b/app/src/helpers/utils/documentUpload.ts @@ -1,9 +1,24 @@ import { PatientDetails } from '../../types/generic/patientDetails'; -import { DOCUMENT_UPLOAD_STATE, UploadDocument } from '../../types/pages/UploadDocumentsPage/types'; -import { generateStitchedFileName } from '../requests/uploadDocuments'; +import { + DOCUMENT_STATUS, + DOCUMENT_UPLOAD_STATE, + UploadDocument, +} from '../../types/pages/UploadDocumentsPage/types'; +import uploadDocuments, { generateStitchedFileName } from '../requests/uploadDocuments'; import { DOCUMENT_TYPE, DOCUMENT_TYPE_CONFIG } from './documentType'; import { v4 as uuidv4 } from 'uuid'; import { zipFiles } from './zip'; +import { isLocal } from './isLocal'; +import { buildMockUploadSession } from '../test/testBuilders'; +import { DocumentStatusResult, UploadSession } from '../../types/generic/uploadResult'; +import { AuthHeaders } from '../../types/blocks/authHeaders'; +import { + DocumentReviewDto, + DocumentReviewStatus, + DocumentReviewStatusDto, +} from '../../types/blocks/documentReview'; +import { uploadDocumentForReview } from '../requests/documentReview'; +import { Dispatch, SetStateAction } from 'react'; export const reduceDocumentsForUpload = async ( documents: UploadDocument[], @@ -49,3 +64,125 @@ export const reduceDocumentsForUpload = async ( return documents; }; + +export const getUploadSession = async ( + patientDetails: PatientDetails, + baseUrl: string, + baseHeaders: AuthHeaders, + existingDocuments: UploadDocument[], + documents: UploadDocument[], + setDocuments: Dispatch>, +): Promise => { + if (isLocal) { + return buildMockUploadSession(documents); + } else if (patientDetails?.canManageRecord) { + return await uploadDocuments({ + nhsNumber: patientDetails.nhsNumber, + documents: documents, + baseUrl, + baseHeaders, + documentReferenceId: existingDocuments[0]?.id, + }); + } else { + const uploadSession: UploadSession = {}; + const requests: Promise[] = []; + + const reviewDocs = documents.map((document) => { + const documentReview = uploadDocumentForReview({ + nhsNumber: patientDetails.nhsNumber, + document, + baseUrl, + baseHeaders, + }); + + documentReview.then((review: DocumentReviewDto) => { + document.id = review.id; + document.versionId = review.version; + uploadSession[review.id] = { + url: review.files[0].presignedUrl, + }; + }); + + requests.push(documentReview); + + return document; + }); + + await Promise.all(requests); + + setDocuments(reviewDocs); + + return uploadSession; + } +}; + +export const handleDocStatusResult = ( + documentStatusResult: DocumentStatusResult, + setDocuments: Dispatch>, +): void => { + setDocuments((previousState) => + previousState.map((doc) => { + const docStatus = documentStatusResult[doc.ref!]; + + const updatedDoc = { + ...doc, + }; + + switch (docStatus?.status) { + case DOCUMENT_STATUS.FINAL: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.SUCCEEDED; + break; + + case DOCUMENT_STATUS.INFECTED: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.INFECTED; + break; + + case DOCUMENT_STATUS.NOT_FOUND: + case DOCUMENT_STATUS.CANCELLED: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.ERROR; + updatedDoc.errorCode = docStatus.error_code; + break; + } + + return updatedDoc; + }), + ); +}; + +export const handleDocReviewStatusResult = ( + result: DocumentReviewStatusDto, + setDocuments: Dispatch>, +): void => { + setDocuments((previousState) => + previousState.map((doc) => { + if (doc.id !== result.id) { + return doc; + } + + const updatedDoc = { + ...doc, + }; + + switch (result.reviewStatus) { + case DocumentReviewStatus.PENDING_REVIEW: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.SUCCEEDED; + break; + + case DocumentReviewStatus.VIRUS_SCAN_FAILED: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.INFECTED; + break; + + case DocumentReviewStatus.REVIEW_PENDING_UPLOAD: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.SCANNING; + break; + + default: + updatedDoc.state = DOCUMENT_UPLOAD_STATE.ERROR; + updatedDoc.errorCode = result.reviewReason; + break; + } + + return updatedDoc; + }), + ); +}; diff --git a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx index af698df941..abd2bf87d8 100644 --- a/app/src/pages/documentUploadPage/DocumentUploadPage.tsx +++ b/app/src/pages/documentUploadPage/DocumentUploadPage.tsx @@ -13,7 +13,7 @@ import useBaseAPIHeaders from '../../helpers/hooks/useBaseAPIHeaders'; import useBaseAPIUrl from '../../helpers/hooks/useBaseAPIUrl'; import useConfig from '../../helpers/hooks/useConfig'; import usePatient from '../../helpers/hooks/usePatient'; -import uploadDocuments, { +import { getDocumentStatus, uploadDocumentToS3, } from '../../helpers/requests/uploadDocuments'; @@ -30,9 +30,8 @@ import { useEnhancedNavigate, } from '../../helpers/utils/urlManipulations'; import { routeChildren, routes } from '../../types/generic/routes'; -import { DocumentStatusResult, UploadSession } from '../../types/generic/uploadResult'; +import { UploadSession } from '../../types/generic/uploadResult'; import { - DOCUMENT_STATUS, DOCUMENT_UPLOAD_STATE, ExistingDocument, LocationParams, @@ -40,9 +39,16 @@ import { UploadDocument, } from '../../types/pages/UploadDocumentsPage/types'; import { DOCUMENT_TYPE, getConfigForDocType } from '../../helpers/utils/documentType'; -import { buildMockUploadSession } from '../../helpers/test/testBuilders'; -import { reduceDocumentsForUpload } from '../../helpers/utils/documentUpload'; +import { + getUploadSession, + handleDocReviewStatusResult, + handleDocStatusResult, + reduceDocumentsForUpload, +} from '../../helpers/utils/documentUpload'; import DocumentUploadIndex from '../../components/blocks/_documentUpload/documentUploadIndex/DocumentUploadIndex'; +import { + getDocumentReviewStatus, +} from '../../helpers/requests/documentReview'; const DocumentUploadPage = (): React.JSX.Element => { const patientDetails = usePatient(); @@ -158,7 +164,7 @@ const DocumentUploadPage = (): React.JSX.Element => { setExistingDocuments(newDocuments); }; - const uploadSingleLloydGeorgeDocument = async ( + const uploadSingleDocument = async ( document: UploadDocument, uploadSession: UploadSession, ): Promise => { @@ -190,7 +196,7 @@ const DocumentUploadPage = (): React.JSX.Element => { uploadSession: UploadSession, ): void => { uploadDocuments.forEach((document) => { - void uploadSingleLloydGeorgeDocument(document, uploadSession); + void uploadSingleDocument(document, uploadSession); }); }; @@ -213,15 +219,14 @@ const DocumentUploadPage = (): React.JSX.Element => { const startUpload = async (): Promise => { try { - const uploadSession: UploadSession = isLocal - ? buildMockUploadSession(documents) - : await uploadDocuments({ - nhsNumber, - documents: documents, - baseUrl, - baseHeaders, - documentReferenceId: existingDocuments[0]?.id, - }); + const uploadSession: UploadSession = await getUploadSession( + patientDetails!, + baseUrl, + baseHeaders, + existingDocuments, + documents, + setDocuments, + ); setUploadSession(uploadSession); const uploadingDocuments = markDocumentsAsUploading(documents, uploadSession); @@ -252,36 +257,6 @@ const DocumentUploadPage = (): React.JSX.Element => { } }; - const handleDocStatusResult = (documentStatusResult: DocumentStatusResult): void => { - setDocuments((previousState) => - previousState.map((doc) => { - const docStatus = documentStatusResult[doc.ref!]; - - const updatedDoc = { - ...doc, - }; - - switch (docStatus?.status) { - case DOCUMENT_STATUS.FINAL: - updatedDoc.state = DOCUMENT_UPLOAD_STATE.SUCCEEDED; - break; - - case DOCUMENT_STATUS.INFECTED: - updatedDoc.state = DOCUMENT_UPLOAD_STATE.INFECTED; - break; - - case DOCUMENT_STATUS.NOT_FOUND: - case DOCUMENT_STATUS.CANCELLED: - updatedDoc.state = DOCUMENT_UPLOAD_STATE.ERROR; - updatedDoc.errorCode = docStatus.error_code; - break; - } - - return updatedDoc; - }), - ); - }; - const startIntervalTimer = (uploadDocuments: Array): number => { return window.setInterval(async () => { interval.current = interval.current + 1; @@ -314,14 +289,25 @@ const DocumentUploadPage = (): React.JSX.Element => { setDocuments(updatedDocuments); } else { try { - const documentStatusResult = await getDocumentStatus({ - documents: uploadDocuments, - baseUrl, - baseHeaders, - nhsNumber, - }); - - handleDocStatusResult(documentStatusResult); + if (patientDetails?.canManageRecord) { + const documentStatusResult = await getDocumentStatus({ + documents: uploadDocuments, + baseUrl, + baseHeaders, + nhsNumber, + }); + + handleDocStatusResult(documentStatusResult, setDocuments); + } else { + uploadDocuments.forEach(async (document) => { + void getDocumentReviewStatus({ + document, + baseUrl, + baseHeaders, + nhsNumber, + }).then((result) => handleDocReviewStatusResult(result, setDocuments)); + }); + } } catch (e) { const error = e as AxiosError; navigate(routes.SERVER_ERROR + errorToParams(error)); diff --git a/app/src/pages/patientResultPage/PatientResultPage.test.tsx b/app/src/pages/patientResultPage/PatientResultPage.test.tsx index aa80bd310f..bd3c3e5ad4 100644 --- a/app/src/pages/patientResultPage/PatientResultPage.test.tsx +++ b/app/src/pages/patientResultPage/PatientResultPage.test.tsx @@ -258,6 +258,33 @@ describe('PatientResultPage', () => { }); }); + it('navigates to upload page after user selects patient they cannot manage when role is GP Clinical and feature flag is enabled', async () => { + const patient = buildPatientDetails({ canManageRecord: false }); + + mockedUsePatient.mockReturnValue(patient); + mockedUseRole.mockReturnValue(REPOSITORY_ROLE.GP_CLINICAL); + mockedUseConfig.mockReturnValue({ + featureFlags: { + uploadLambdaEnabled: true, + uploadArfWorkflowEnabled: false, + uploadLloydGeorgeWorkflowEnabled: true, + uploadDocumentIteration3Enabled: true, + }, + mockLocal: {}, + }); + + render(); + await userEvent.click( + screen.getByRole('button', { + name: CONFIRM_BUTTON_TEXT, + }), + ); + + await waitFor(() => { + expect(mockedUseNavigate).toHaveBeenCalledWith(routes.DOCUMENT_UPLOAD); + }); + }); + it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])( "navigates to Lloyd George Record page after user selects Active patient, when role is '%s' and uploadDocumentIteration3Enabled is false", async (role) => { @@ -276,7 +303,7 @@ describe('PatientResultPage', () => { ); it.each([REPOSITORY_ROLE.GP_ADMIN, REPOSITORY_ROLE.GP_CLINICAL])( - "navigates to patient documents page after user selects Active patient, when role is '%s' and uploadDocumentIteration3Enabled is true", + "navigates to patient documents page after user selects Active patient and canManageRecord, when role is '%s' and uploadDocumentIteration3Enabled is true", async (role) => { const patient = buildPatientDetails({ active: true }); mockedUseRole.mockReturnValue(role); diff --git a/app/src/pages/patientResultPage/PatientResultPage.tsx b/app/src/pages/patientResultPage/PatientResultPage.tsx index 0709b302ed..1505cad42d 100644 --- a/app/src/pages/patientResultPage/PatientResultPage.tsx +++ b/app/src/pages/patientResultPage/PatientResultPage.tsx @@ -37,6 +37,11 @@ const PatientResultPage = (): React.JSX.Element => { return; } + if (!patientDetails.canManageRecord && featureFlags.uploadDocumentIteration3Enabled) { + navigate(routes.DOCUMENT_UPLOAD); + return; + } + if (patientDetails?.active) { navigate( featureFlags.uploadDocumentIteration3Enabled diff --git a/app/src/types/blocks/documentReview.ts b/app/src/types/blocks/documentReview.ts index 8e808f295c..b41fb15235 100644 --- a/app/src/types/blocks/documentReview.ts +++ b/app/src/types/blocks/documentReview.ts @@ -15,7 +15,7 @@ type DocumentReviewFile = { export type DocumentReviewStatusDto = { id: string; - status: string; + reviewStatus: string; version: string; reviewReason: string; }; diff --git a/app/src/types/generic/patientDetails.ts b/app/src/types/generic/patientDetails.ts index 1905c9bced..2df0fac4d3 100644 --- a/app/src/types/generic/patientDetails.ts +++ b/app/src/types/generic/patientDetails.ts @@ -8,4 +8,5 @@ export type PatientDetails = { restricted: boolean; active: boolean; deceased: boolean; + canManageRecord?: boolean; }; diff --git a/app/src/types/generic/uploadResult.ts b/app/src/types/generic/uploadResult.ts index 42badf415c..b8f0911397 100644 --- a/app/src/types/generic/uploadResult.ts +++ b/app/src/types/generic/uploadResult.ts @@ -4,7 +4,7 @@ export type UploadSession = { export type S3Upload = { url: string; - fields: S3UploadFields; + fields?: S3UploadFields; }; export type S3UploadFields = {