diff --git a/app/src/components/blocks/_admin/reviewDetailsAssessmentStage/ReviewDetailsAssessmentStage.test.tsx b/app/src/components/blocks/_admin/reviewDetailsAssessmentStage/ReviewDetailsAssessmentStage.test.tsx
index 405a686023..ca605ad2ff 100644
--- a/app/src/components/blocks/_admin/reviewDetailsAssessmentStage/ReviewDetailsAssessmentStage.test.tsx
+++ b/app/src/components/blocks/_admin/reviewDetailsAssessmentStage/ReviewDetailsAssessmentStage.test.tsx
@@ -12,6 +12,7 @@ import {
import { ReviewDetails } from '../../../../types/generic/reviews';
import * as getReviewsModule from '../../../../helpers/requests/getReviews';
import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
+import { buildPatientDetails } from '../../../../helpers/test/testBuilders';
const mockedUseNavigate = vi.fn();
const mockSetPatientDetails = vi.fn();
@@ -375,7 +376,7 @@ describe('ReviewDetailsAssessmentPage', () => {
});
it('displays patient demographics', async () => {
- mockUsePatientDetailsContext.mockReturnValue([null, mockSetPatientDetails]);
+ mockUsePatientDetailsContext.mockReturnValue([buildPatientDetails(), mockSetPatientDetails]);
render(
({
@@ -134,7 +135,9 @@ describe('ReviewDetailsFileSelectStage', () => {
expect(screen.queryByText('existing.pdf')).not.toBeInTheDocument();
});
- it('renders patient demograhics', () => {
+ it('renders patient demographics', () => {
+ mockUsePatientDetailsContext.mockReturnValue([buildPatientDetails(), mockSetPatientDetails]);
+
const documents: ReviewUploadDocument[] = [
makeReviewDoc('file1.pdf', DOCUMENT_UPLOAD_STATE.UNSELECTED, UploadDocumentType.REVIEW),
makeReviewDoc(
@@ -144,7 +147,7 @@ describe('ReviewDetailsFileSelectStage', () => {
),
];
- renderApp({ reviewData: mockReviewData, uploadDocuments: documents });
+ renderApp({ reviewData: mockReviewData, uploadDocuments: documents, mockPatientContext: false });
expect(screen.getByTestId('patient-summary')).toBeInTheDocument();
expect(screen.getByTestId('patient-summary-full-name')).toBeInTheDocument();
diff --git a/app/src/components/blocks/_admin/reviewsDetailsStage/ReviewsDetailsStage.tsx b/app/src/components/blocks/_admin/reviewsDetailsStage/ReviewsDetailsStage.tsx
index 2a04b7d1a4..78001edb14 100644
--- a/app/src/components/blocks/_admin/reviewsDetailsStage/ReviewsDetailsStage.tsx
+++ b/app/src/components/blocks/_admin/reviewsDetailsStage/ReviewsDetailsStage.tsx
@@ -302,7 +302,7 @@ const ReviewsDetailsStage = ({
)}
}
isFullScreen={session.isFullscreen || false}
diff --git a/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.test.tsx b/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.test.tsx
index 8b79f86385..bdccd3e552 100644
--- a/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.test.tsx
+++ b/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.test.tsx
@@ -209,21 +209,21 @@ describe('ReviewsPage', () => {
if (snomed === '16521000000101') {
return {
content: {
- reviewList: 'Lloyd George',
+ reviewDocumentTitle: 'Scanned paper notes',
},
- displayName: 'Lloyd George Record',
+ displayName: 'scanned paper notes',
};
} else if (snomed === '717391000000106') {
return {
content: {
- reviewList: 'Electronic Health Record',
+ reviewDocumentTitle: 'Electronic health record',
},
- displayName: 'Electronic Health Record',
+ displayName: 'electronic health record',
};
}
return {
content: {
- reviewList: 'Unknown Type',
+ reviewDocumentTitle: 'Unknown Type',
},
displayName: 'Unknown Type',
};
@@ -289,8 +289,8 @@ describe('ReviewsPage', () => {
});
expect(screen.getByText('900 000 0002')).toBeInTheDocument();
- expect(screen.getByText('Lloyd George')).toBeInTheDocument();
- expect(screen.getByText('Electronic Health Record')).toBeInTheDocument();
+ expect(screen.getByText('Scanned paper notes')).toBeInTheDocument();
+ expect(screen.getByText('Electronic health record')).toBeInTheDocument();
expect(screen.getByText('Y12345')).toBeInTheDocument();
expect(screen.getByText('Y67890')).toBeInTheDocument();
});
@@ -334,10 +334,10 @@ describe('ReviewsPage', () => {
renderComponent();
await waitFor(() => {
- expect(screen.getByText('Lloyd George')).toBeInTheDocument();
+ expect(screen.getByText('Scanned paper notes')).toBeInTheDocument();
});
- expect(screen.getByText('Electronic Health Record')).toBeInTheDocument();
+ expect(screen.getByText('Electronic health record')).toBeInTheDocument();
});
it('displays "Unknown Type" for unrecognized SNOMED codes', async () => {
diff --git a/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.tsx b/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.tsx
index a037f99f9a..3d6f9bddbd 100644
--- a/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.tsx
+++ b/app/src/components/blocks/_admin/reviewsPage/ReviewsPage.tsx
@@ -219,7 +219,7 @@ export const ReviewsPage = ({ setReviewData }: ReviewsPageProps): React.JSX.Elem
let recordType: string = '';
try {
recordType = getConfigForDocType(dto.documentSnomedCodeType).content
- .reviewList as string;
+ .reviewDocumentTitle as string;
} catch {}
return {
diff --git a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
index 058d6174c9..0ca123f419 100644
--- a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
+++ b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.test.tsx
@@ -167,6 +167,26 @@ describe('DeleteSubmitStage', () => {
});
});
+ it('renders DeletionCompleteStage when the Yes is selected and Continue clicked, when user role is PCSE and feature flag is disabled', async () => {
+ mockedUseRole.mockReturnValue(REPOSITORY_ROLE.PCSE);
+
+ mockedAxios.delete.mockReturnValue(Promise.resolve({ status: 200, data: 'Success' }));
+
+ renderComponent(DOCUMENT_TYPE.LLOYD_GEORGE, history);
+
+ expect(screen.getByRole('radio', { name: 'Yes' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Continue' })).toBeInTheDocument();
+
+ await userEvent.click(screen.getByRole('radio', { name: 'Yes' }));
+ await userEvent.click(screen.getByRole('button', { name: 'Continue' }));
+
+ await waitFor(() => {
+ expect(mockedUseNavigate).toHaveBeenCalledWith(
+ routeChildren.DOCUMENT_DELETE_COMPLETE,
+ );
+ });
+ });
+
it('renders DeletionCompleteStage when the Yes is selected and Continue clicked, when user role is GP_ADMIN and feature flag is enabled', async () => {
mockedUseRole.mockReturnValue(REPOSITORY_ROLE.GP_ADMIN);
mockuseConfig.mockReturnValue({
diff --git a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
index 1b34f0b91d..d2c87fd7ec 100644
--- a/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
+++ b/app/src/components/blocks/_delete/deleteSubmitStage/DeleteSubmitStage.tsx
@@ -84,7 +84,7 @@ export const DeleteSubmitStageIndexView = ({
resetDocState();
setDeletionStage(SUBMISSION_STATE.SUCCEEDED);
navigate(
- config.featureFlags.uploadDocumentIteration3Enabled
+ config.featureFlags.uploadDocumentIteration3Enabled || role === REPOSITORY_ROLE.PCSE
? routeChildren.DOCUMENT_DELETE_COMPLETE
: routeChildren.LLOYD_GEORGE_DELETE_COMPLETE,
);
diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
index f0168385c7..708e94bc4e 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.test.tsx
@@ -71,6 +71,15 @@ describe('DocumentUploadCompleteStage', () => {
const expectedDob = getFormattedDate(new Date(patientDetails.birthDate));
expect(screen.getByTestId('dob').textContent).toEqual('Date of birth: ' + expectedDob);
+
+ expect(
+ screen.queryByText(
+ 'You are not the data controller',
+ {
+ exact: false,
+ }
+ ),
+ ).not.toBeInTheDocument();
});
it('should navigate to search when clicking the search link', async () => {
@@ -153,6 +162,25 @@ describe('DocumentUploadCompleteStage', () => {
expect(screen.getByText(documents[1].file.name)).toBeInTheDocument();
});
+ it('should render non-data controller message when user is not data controller', async () => {
+ vi.mocked(usePatient).mockReturnValueOnce(
+ buildPatientDetails({
+ canManageRecord: false,
+ }),
+ );
+
+ renderApp(documents);
+
+ expect(
+ screen.getByText(
+ 'You are not the data controller',
+ {
+ exact: false,
+ }
+ ),
+ ).toBeInTheDocument();
+ });
+
const renderApp = (documents: UploadDocument[]) => {
render(
diff --git a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
index ae8de73e2b..d44d78a0ef 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadCompleteStage/DocumentUploadCompleteStage.tsx
@@ -126,6 +126,13 @@ const DocumentUploadCompleteStage = ({ documents, documentConfig }: Props): Reac
)}
+ {patientDetails.canManageRecord === false && (
+
+ You are not the data controller for this patient so you cannot view the files
+ you have uploaded in this service.
+
+ )}
+
If you think you've made a mistake, contact the Patient Record Management team at{' '}
england.prmteam@nhs.net.
diff --git a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
index 1db4f6ed7a..cfe7e6e535 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.test.tsx
@@ -2,7 +2,7 @@ import { render, waitFor, screen, RenderResult } from '@testing-library/react';
import DocumentUploadConfirmStage from './DocumentUploadConfirmStage';
import { formatNhsNumber } from '../../../../helpers/utils/formatNhsNumber';
import { getFormattedDate } from '../../../../helpers/utils/formatDate';
-import { buildDocumentConfig, buildPatientDetails } from '../../../../helpers/test/testBuilders';
+import { buildDocument, buildDocumentConfig, buildPatientDetails } from '../../../../helpers/test/testBuilders';
import usePatient from '../../../../helpers/hooks/usePatient';
import {
DOCUMENT_UPLOAD_STATE,
@@ -11,7 +11,6 @@ import {
import * as ReactRouter from 'react-router-dom';
import { MemoryHistory, createMemoryHistory } from 'history';
import userEvent from '@testing-library/user-event';
-import { routeChildren, routes } from '../../../../types/generic/routes';
import { getFormattedPatientFullName } from '../../../../helpers/utils/formatPatientFullName';
import { DOCUMENT_TYPE } from '../../../../helpers/utils/documentType';
import { getJourney } from '../../../../helpers/utils/urlManipulations';
@@ -34,22 +33,6 @@ vi.mock('react-router-dom', async () => {
const mockedUseNavigate = vi.fn();
-vi.mock('./components/DocumentList', async () => {
- const actual = await vi.importActual('./components/DocumentList');
- return {
- ...actual,
- default: ({ documents }: { documents: UploadDocument[] }): React.JSX.Element => {
- return (
-
- Document List with
- {documents.length}
- documents
-
- );
- },
- };
-});
-
vi.mock('../documentUploadLloydGeorgePreview/DocumentUploadLloydGeorgePreview', () => ({
default: ({
documents,
@@ -80,9 +63,10 @@ let history = createMemoryHistory({
initialIndex: 0,
});
-let docConfig = buildDocumentConfig();
const mockConfirmFiles = vi.fn();
+let mockDocuments: UploadDocument[] = [];
+
describe('DocumentUploadConfirmStage', () => {
beforeEach(() => {
vi.mocked(usePatient).mockReturnValue(patientDetails);
@@ -92,10 +76,11 @@ describe('DocumentUploadConfirmStage', () => {
});
afterEach(() => {
vi.clearAllMocks();
+ mockDocuments = [];
});
it('renders', async () => {
- renderApp(history, 1);
+ renderApp(history);
await waitFor(async () => {
expect(screen.getByText('Check files are for the correct patient')).toBeInTheDocument();
@@ -103,7 +88,7 @@ describe('DocumentUploadConfirmStage', () => {
});
it('should call confirmFiles when confirm button is clicked', async () => {
- renderApp(history, 1);
+ renderApp(history);
await userEvent.click(await screen.findByTestId('confirm-button'));
@@ -113,17 +98,19 @@ describe('DocumentUploadConfirmStage', () => {
});
it.each([
- { fileCount: 3, expectedPreviewCount: 3, stitched: true },
- { fileCount: 1, expectedPreviewCount: 1, stitched: false },
+ { fileCount: 3, expectedPreviewCount: 3, docType: DOCUMENT_TYPE.LLOYD_GEORGE },
+ { fileCount: 1, expectedPreviewCount: 1, docType: DOCUMENT_TYPE.EHR },
])(
'should render correct number files in the preview %s',
- async ({ fileCount, expectedPreviewCount, stitched }) => {
- docConfig = buildDocumentConfig({
- snomedCode: DOCUMENT_TYPE.EHR,
- stitched,
- });
-
- renderApp(history, fileCount);
+ async ({ fileCount, expectedPreviewCount, docType }) => {
+ for (let i = 1; i <= fileCount; i++) {
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 1.pdf`, { type: 'application/pdf' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ docType,
+ ));
+ }
+ renderApp(history);
await waitFor(async () => {
expect(screen.getByTestId('lloyd-george-preview-count').textContent).toBe(
@@ -133,9 +120,68 @@ describe('DocumentUploadConfirmStage', () => {
},
);
+ it('should hide preview when the previewed document is removed', async () => {
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 1.pdf`, { type: 'application/pdf' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ ));
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 2.pdf`, { type: 'application/pdf' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ ));
+ renderApp(history);
+
+ const firstDocumentViewButton = await screen.findByTestId(`preview-${mockDocuments[0].id}-button`);
+ expect(firstDocumentViewButton).toBeInTheDocument();
+
+ await userEvent.click(firstDocumentViewButton);
+
+ await waitFor(() => {
+ expect(screen.getByTestId('lloyd-george-preview')).toBeInTheDocument();
+ });
+
+ const removeButton = screen.getByTestId(`remove-${mockDocuments[0].id}-button`);
+ expect(removeButton).toBeInTheDocument();
+
+ await userEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(screen.queryByTestId('lloyd-george-preview')).not.toBeInTheDocument();
+ });
+ });
+
+ it('should show preview when 1 pdf remains after removing a document', async () => {
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 1.pdf`, { type: 'application/pdf' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ ));
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 2.txt`, { type: 'text/plain' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ DOCUMENT_TYPE.EHR_ATTACHMENTS,
+ ));
+ renderApp(history);
+
+ await waitFor(() => {
+ expect(screen.queryByTestId('lloyd-george-preview')).not.toBeInTheDocument();
+ });
+
+ const removeButton = screen.getByTestId(`remove-${mockDocuments[0].id}-button`);
+ expect(removeButton).toBeInTheDocument();
+
+ await userEvent.click(removeButton);
+
+ await waitFor(() => {
+ expect(screen.queryByTestId('lloyd-george-preview')).not.toBeInTheDocument();
+ });
+ });
+
describe('Navigation', () => {
it('should navigate to previous screen when go back is clicked', async () => {
- renderApp(history, 1);
+ renderApp(history);
userEvent.click(await screen.findByTestId('go-back-link'));
@@ -145,7 +191,7 @@ describe('DocumentUploadConfirmStage', () => {
});
it('renders patient summary fields is inset', async () => {
- renderApp(history, 1);
+ renderApp(history);
const insetText = screen
.getByText('Make sure that all files uploaded are for this patient only:')
@@ -179,7 +225,7 @@ describe('DocumentUploadConfirmStage', () => {
});
it('should still render all page elements correctly', async () => {
- renderApp(history, 1);
+ renderApp(history);
await waitFor(async () => {
expect(
@@ -191,22 +237,19 @@ describe('DocumentUploadConfirmStage', () => {
});
});
- const renderApp = (history: MemoryHistory, docsLength: number): RenderResult => {
- const documents: UploadDocument[] = [];
- for (let i = 1; i <= docsLength; i++) {
- documents.push({
- attempts: 0,
- id: `${i}`,
- docType: DOCUMENT_TYPE.LLOYD_GEORGE,
- file: new File(['file'], `file ${i}.pdf`, { type: 'application/pdf' }),
- state: DOCUMENT_UPLOAD_STATE.SELECTED,
- });
+ const renderApp = (history: MemoryHistory): RenderResult => {
+ if (mockDocuments.length === 0) {
+ mockDocuments.push(buildDocument(
+ new File(['file'], `file 1.pdf`, { type: 'application/pdf' }),
+ DOCUMENT_UPLOAD_STATE.SELECTED,
+ DOCUMENT_TYPE.LLOYD_GEORGE,
+ ));
}
return render(
{}}
/>
diff --git a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
index 0631223df8..afb8ae35ff 100644
--- a/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
+++ b/app/src/components/blocks/_documentUpload/documentUploadConfirmStage/DocumentUploadConfirmStage.tsx
@@ -114,8 +114,10 @@ const DocumentUploadConfirmStage = ({
setCurrentPreviewDocument(document);
// timeout to wait for first render before scrolling
setTimeout(() => {
- documentPreviewRef.current?.scrollIntoView({ behavior: 'smooth' });
- }, 0);
+ if (typeof documentPreviewRef?.current?.scrollIntoView === 'function') {
+ documentPreviewRef?.current?.scrollIntoView({ behavior: 'smooth' });
+ }
+ }, 2);
};
const removeDocument = (docToRemove: UploadDocument): void => {
@@ -123,7 +125,10 @@ const DocumentUploadConfirmStage = ({
groupDocumentsByType(updatedDocs);
- if (updatedDocs.length === 1) {
+ if (currentPreviewDocument?.id === docToRemove.id) {
+ setCurrentPreviewDocument(undefined);
+ }
+ else if (updatedDocs.length === 1 && updatedDocs[0].file.type === 'application/pdf') {
setCurrentPreviewDocument(updatedDocs[0]);
}
@@ -156,7 +161,7 @@ const DocumentUploadConfirmStage = ({
const documentPreview = (): React.JSX.Element => {
if (!currentPreviewDocument) {
- return <>>;
+ return ;
}
const config = getConfigForDocType(currentPreviewDocument.docType);
@@ -170,8 +175,7 @@ const DocumentUploadConfirmStage = ({
const showCurrentlyViewingText =
hasUnstitchedDocType &&
documents.length > 0 &&
- !!groupedDocuments &&
- Object.keys(groupedDocuments).length > 1;
+ !!groupedDocuments;
return (
diff --git a/app/src/components/generic/patientSummary/PatientSummary.test.tsx b/app/src/components/generic/patientSummary/PatientSummary.test.tsx
index 867998cc87..5692c53951 100644
--- a/app/src/components/generic/patientSummary/PatientSummary.test.tsx
+++ b/app/src/components/generic/patientSummary/PatientSummary.test.tsx
@@ -16,6 +16,12 @@ describe('PatientSummary', () => {
vi.clearAllMocks();
});
+ it('renders nothing when patient details are null', () => {
+ mockedUsePatient.mockReturnValue(null);
+ const { container } = render(
);
+ expect(container).toBeEmptyDOMElement();
+ });
+
it('renders provided patient information', () => {
const mockDetails = buildPatientDetails({
familyName: 'Jones',
@@ -244,21 +250,6 @@ describe('PatientSummary', () => {
expect(screen.getByText('First name')).toBeInTheDocument();
});
- it('handles null patient details gracefully', () => {
- mockedUsePatient.mockReturnValue(null);
- render(
-
-
-
-
-
- ,
- );
-
- expect(screen.getByTestId('patient-summary')).toBeInTheDocument();
- // Components should render but with empty/default values
- });
-
it('handles empty NHS number', () => {
const mockDetails = buildPatientDetails({
nhsNumber: '',
diff --git a/app/src/components/generic/patientSummary/PatientSummary.tsx b/app/src/components/generic/patientSummary/PatientSummary.tsx
index 82b41726a6..bbabdaf235 100644
--- a/app/src/components/generic/patientSummary/PatientSummary.tsx
+++ b/app/src/components/generic/patientSummary/PatientSummary.tsx
@@ -133,8 +133,13 @@ const PatientSummary = ({
if (reviewPatientDetails) {
patientDetails = reviewPatientDetails;
}
+
const patientDetailsContextValue = useMemo(() => ({ patientDetails }), [patientDetails]);
+ if (!patientDetails) {
+ return <>>;
+ }
+
if (oneLine) {
const nameLengthLimit = 30;
const givenName = patientDetails?.givenName.join(' ') || '';
@@ -168,11 +173,11 @@ const PatientSummary = ({
data-testid="patient-summary-small-nhs-number"
className="nhsuk-u-padding-right-9"
>
- NHS number: {formatNhsNumber(patientDetails!.nhsNumber)}
+ NHS number: {formatNhsNumber(patientDetails.nhsNumber)}
Date of birth:{' '}
- {getFormattedDate(new Date(patientDetails!.birthDate))}
+ {getFormattedDate(new Date(patientDetails.birthDate))}
diff --git a/app/src/config/documentTypesConfig.json b/app/src/config/documentTypesConfig.json
index 6b27a05e78..4c1b639cae 100644
--- a/app/src/config/documentTypesConfig.json
+++ b/app/src/config/documentTypesConfig.json
@@ -28,5 +28,15 @@
"upload_title": "",
"upload_description": ""
}
+ },
+ {
+ "name": "Letters and Documents",
+ "snomed_code": "162931000000103",
+ "config_name": "lettersAndDocumentsConfig",
+ "canUploadIndependently": true,
+ "content": {
+ "upload_title": "Other documents",
+ "upload_description": "Upload other letters and documents that have arrived for this patient after they have left your practice. For example, letters, test results and referrals."
+ }
}
]
\ No newline at end of file
diff --git a/app/src/config/electronicHealthRecordAttachmentsConfig.json b/app/src/config/electronicHealthRecordAttachmentsConfig.json
index bd038d2c63..368beeaa26 100644
--- a/app/src/config/electronicHealthRecordAttachmentsConfig.json
+++ b/app/src/config/electronicHealthRecordAttachmentsConfig.json
@@ -32,7 +32,7 @@
"beforeYouUploadTitle": "Before you upload",
"previewUploadTitle": "Preview electronic health record attachment",
"uploadFilesExtraParagraph": "",
- "reviewList": "EHR Attachments",
+ "reviewDocumentTitle": "EHR Attachments",
"skipDocumentLinkText": "Continue without uploading any EHR attachments"
}
}
\ No newline at end of file
diff --git a/app/src/config/electronicHealthRecordConfig.json b/app/src/config/electronicHealthRecordConfig.json
index 8fd70b0703..483061a47d 100644
--- a/app/src/config/electronicHealthRecordConfig.json
+++ b/app/src/config/electronicHealthRecordConfig.json
@@ -35,7 +35,7 @@
"beforeYouUploadTitle": "Before you upload",
"previewUploadTitle": "Preview this electronic health record",
"uploadFilesExtraParagraph": "",
- "reviewList": "Electronic Health Record",
+ "reviewDocumentTitle": "Electronic health record",
"skipDocumentLinkText": "Continue without uploading EHR notes"
}
}
\ No newline at end of file
diff --git a/app/src/config/lettersAndDocumentsConfig.json b/app/src/config/lettersAndDocumentsConfig.json
new file mode 100644
index 0000000000..0f00538476
--- /dev/null
+++ b/app/src/config/lettersAndDocumentsConfig.json
@@ -0,0 +1,36 @@
+{
+ "snomedCode": "162931000000103",
+ "displayName": "other docs and letters",
+ "filenameOverride": "",
+ "canBeUpdated": false,
+ "associatedSnomed": "",
+ "multifileUpload": true,
+ "multifileZipped": false,
+ "multifileReview": false,
+ "canBeDiscarded": true,
+ "stitched": false,
+ "stitchedFilenamePrefix": "",
+ "singleDocumentOnly": false,
+ "acceptedFileTypes": [],
+ "content": {
+ "viewDocumentTitle": "Letters and documents",
+ "addFilesSelectTitle": "",
+ "uploadFilesSelectTitle": "Choose documents to upload",
+ "uploadFilesBulletPoints": [
+ "There is no maximum number of size of files you can upload",
+ "You can upload files in any format except .zip and .exe files",
+ "If there is a problem with your files during upload, you'll need to resolve these before continuing"
+ ],
+ "chooseFilesMessage": "Choose files to upload",
+ "chooseFilesButtonLabel": "Choose files",
+ "chooseFilesWarningText": "",
+ "skipDocumentLinkText": "",
+ "confirmFilesTitle": "Check files are for the correct patient",
+ "confirmFilesTableTitle": "Files to upload",
+ "confirmFilesTableParagraph": "",
+ "beforeYouUploadTitle": "Before you upload",
+ "previewUploadTitle": "Preview your PDF files",
+ "uploadFilesExtraParagraph": "",
+ "reviewDocumentTitle": "Letters and documents"
+ }
+}
\ No newline at end of file
diff --git a/app/src/config/lloydGeorgeConfig.json b/app/src/config/lloydGeorgeConfig.json
index de6e325866..124e7f184f 100644
--- a/app/src/config/lloydGeorgeConfig.json
+++ b/app/src/config/lloydGeorgeConfig.json
@@ -31,8 +31,8 @@
"confirmFilesTableTitle": "Scanned paper notes to upload",
"confirmFilesTableParagraph": "",
"beforeYouUploadTitle": "Before you upload",
- "previewUploadTitle": "Preview this scanned paper notes record",
+ "previewUploadTitle": "Preview these scanned paper notes",
"uploadFilesExtraParagraph": "You can add a note to the patient's electronic health record to say their Lloyd George record is stored in this service. Use SNOMED code 16521000000101.",
- "reviewList": "Scanned Paper Notes"
+ "reviewDocumentTitle": "Scanned paper notes"
}
}
\ No newline at end of file
diff --git a/app/src/config/rejectedFileTypes.json b/app/src/config/rejectedFileTypes.json
index ba34252e99..5bd427ed80 100644
--- a/app/src/config/rejectedFileTypes.json
+++ b/app/src/config/rejectedFileTypes.json
@@ -1,7 +1,9 @@
[
+ "7Z",
"ACTION",
"APK",
"APP",
+ "B6Z",
"BAT",
"BIN",
"CAB",
@@ -10,18 +12,24 @@
"COMMAND",
"CPL",
"CSH",
+ "DLL",
+ "DMG",
"EX_",
"EXE",
"GADGET",
+ "GZ",
"INF1",
"INS",
"INX",
"IPA",
"ISU",
+ "ISO",
+ "JAR",
"JOB",
"JSE",
"KSH",
"LNK",
+ "LZ",
"MSC",
"MSI",
"MSP",
@@ -32,13 +40,20 @@
"PIF",
"PRG",
"PS1",
+ "RAR",
"REG",
"RGS",
"RUN",
+ "S7Z",
"SCR",
"SCT",
"SHB",
"SHS",
+ "TAR",
+ "TBZ2",
+ "TGZ",
+ "TLZ",
+ "TX7",
"U3P",
"VB",
"VBE",
@@ -47,5 +62,11 @@
"WORKFLOW",
"WS",
"WSF",
- "WSH"
+ "WSH",
+ "X7",
+ "Z",
+ "ZIP",
+ "ZIPX",
+ "ZST",
+ "ZZ"
]
\ No newline at end of file
diff --git a/app/src/helpers/utils/documentType.test.ts b/app/src/helpers/utils/documentType.test.ts
index a325796207..50d1ca4c5f 100644
--- a/app/src/helpers/utils/documentType.test.ts
+++ b/app/src/helpers/utils/documentType.test.ts
@@ -50,6 +50,13 @@ describe('documentType', () => {
expect(config.multifileUpload).toBe(true);
});
+ it('should return config for LETTERS_AND_DOCS', () => {
+ const config = getConfigForDocType(DOCUMENT_TYPE.LETTERS_AND_DOCS);
+ expect(config.snomedCode).toBe(DOCUMENT_TYPE.LETTERS_AND_DOCS);
+ expect(config.displayName).toBe('other docs and letters');
+ expect(config.multifileUpload).toBe(true);
+ });
+
it('should throw error for unknown document type', () => {
expect(() => getConfigForDocType('unknown' as DOCUMENT_TYPE)).toThrow(
'No config found for document type: unknown',
diff --git a/app/src/helpers/utils/documentType.ts b/app/src/helpers/utils/documentType.ts
index 9b1b068e20..f34d075b8e 100644
--- a/app/src/helpers/utils/documentType.ts
+++ b/app/src/helpers/utils/documentType.ts
@@ -1,6 +1,7 @@
import lloydGeorgeConfig from '../../config/lloydGeorgeConfig.json';
import electronicHealthRecordConfig from '../../config/electronicHealthRecordConfig.json';
import electronicHealthRecordAttachmentsConfig from '../../config/electronicHealthRecordAttachmentsConfig.json';
+import lettersAndDocumentsConfig from '../../config/lettersAndDocumentsConfig.json';
export enum DOCUMENT_TYPE {
LLOYD_GEORGE = '16521000000101',
@@ -11,7 +12,7 @@ export enum DOCUMENT_TYPE {
}
export type ContentKey =
- | 'reviewList'
+ | 'reviewDocumentTitle'
| 'viewDocumentTitle'
| 'addFilesSelectTitle'
| 'uploadFilesSelectTitle'
@@ -83,6 +84,8 @@ export const getConfigForDocType = (docType: DOCUMENT_TYPE): DOCUMENT_TYPE_CONFI
return electronicHealthRecordConfig as DOCUMENT_TYPE_CONFIG;
case DOCUMENT_TYPE.EHR_ATTACHMENTS:
return electronicHealthRecordAttachmentsConfig as DOCUMENT_TYPE_CONFIG;
+ case DOCUMENT_TYPE.LETTERS_AND_DOCS:
+ return lettersAndDocumentsConfig as DOCUMENT_TYPE_CONFIG;
default:
throw new Error(`No config found for document type: ${docType}`);
}
diff --git a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
index 648d5f990f..f1df6535ca 100644
--- a/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
+++ b/app/src/pages/documentSearchResultsPage/DocumentSearchResultsPage.tsx
@@ -87,6 +87,16 @@ const DocumentSearchResultsPage = (): React.JSX.Element => {
fileName: 'EHR Attachments.zip',
contentType: 'application/zip',
}),
+ buildSearchResult({
+ documentSnomedCodeType: DOCUMENT_TYPE.LETTERS_AND_DOCS,
+ fileName: 'Later letter.pdf',
+ contentType: 'application/pdf',
+ }),
+ buildSearchResult({
+ documentSnomedCodeType: DOCUMENT_TYPE.LETTERS_AND_DOCS,
+ fileName: 'Later letter 2.docx',
+ contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+ }),
]);
setSubmissionState(SUBMISSION_STATE.SUCCEEDED);
} else {
diff --git a/lambdas/enums/metadata_field_names.py b/lambdas/enums/metadata_field_names.py
index f11a8a9181..75439c3e86 100755
--- a/lambdas/enums/metadata_field_names.py
+++ b/lambdas/enums/metadata_field_names.py
@@ -19,6 +19,7 @@ class DocumentReferenceMetadataFields(Enum):
DOC_STATUS = "DocStatus"
CUSTODIAN = "Custodian"
DOCUMENT_SCAN_CREATION = "DocumentScanCreation"
+ DOCUMENT_SNOMED_CODE_TYPE = "DocumentSnomedCodeType"
@staticmethod
def list() -> list[str]:
diff --git a/lambdas/enums/snomed_codes.py b/lambdas/enums/snomed_codes.py
index e40f2fdf4b..4af5726027 100644
--- a/lambdas/enums/snomed_codes.py
+++ b/lambdas/enums/snomed_codes.py
@@ -21,6 +21,9 @@ class SnomedCodes(Enum):
EHR_ATTACHMENTS = SnomedCode(
code="24511000000107", display_name="Electronic Health Record Attachments"
)
+ LETTERS_AND_DOCUMENTS = SnomedCode(
+ code="162931000000103", display_name="Care record elements"
+ )
# Temporary snomed code used.
PATIENT_DATA = SnomedCode(
code="717391000000106", display_name="Confidential patient data"
diff --git a/lambdas/enums/supported_document_types.py b/lambdas/enums/supported_document_types.py
index db12e1f411..1e05bcde07 100644
--- a/lambdas/enums/supported_document_types.py
+++ b/lambdas/enums/supported_document_types.py
@@ -12,6 +12,7 @@ class SupportedDocumentTypes(StrEnum):
LG = SnomedCodes.LLOYD_GEORGE.value.code
EHR = SnomedCodes.EHR.value.code
EHR_ATTACHMENTS = SnomedCodes.EHR_ATTACHMENTS.value.code
+ LETTERS_AND_DOCUMENTS = SnomedCodes.LETTERS_AND_DOCUMENTS.value.code
@staticmethod
def list():
@@ -39,6 +40,9 @@ def get_dynamodb_table_name(self) -> str:
SupportedDocumentTypes.EHR_ATTACHMENTS: os.getenv(
"LLOYD_GEORGE_DYNAMODB_NAME"
),
+ SupportedDocumentTypes.LETTERS_AND_DOCUMENTS: os.getenv(
+ "LLOYD_GEORGE_BUCKET_NAME"
+ ),
}
return document_type_to_table_name[self]
@@ -50,5 +54,8 @@ def get_s3_bucket_name(self) -> str:
SupportedDocumentTypes.EHR_ATTACHMENTS: os.getenv(
"LLOYD_GEORGE_BUCKET_NAME"
),
+ SupportedDocumentTypes.LETTERS_AND_DOCUMENTS: os.getenv(
+ "LLOYD_GEORGE_BUCKET_NAME"
+ ),
}
return lookup_dict[self]
diff --git a/lambdas/enums/upload_forbidden_file_extensions.py b/lambdas/enums/upload_forbidden_file_extensions.py
index 9684f89e8c..6b87ee7cdd 100644
--- a/lambdas/enums/upload_forbidden_file_extensions.py
+++ b/lambdas/enums/upload_forbidden_file_extensions.py
@@ -7,6 +7,7 @@ class ForbiddenFileType(StrEnum):
ACTION = "ACTION"
APK = "APK"
APP = "APP"
+ B6Z = "B6Z"
BAT = "BAT"
BIN = "BIN"
CAB = "CAB"
@@ -15,18 +16,24 @@ class ForbiddenFileType(StrEnum):
COMMAND = "COMMAND"
CPL = "CPL"
CSH = "CSH"
- EX_ = "EX_"
+ DLL = "DLL"
+ DMG = "DMG"
EXE = "EXE"
+ EX_ = "EX_"
GADGET = "GADGET"
+ GZ = "GZ"
INF1 = "INF1"
INS = "INS"
INX = "INX"
IPA = "IPA"
+ ISO = "ISO"
ISU = "ISU"
+ JAR = "JAR"
JOB = "JOB"
JSE = "JSE"
KSH = "KSH"
LNK = "LNK"
+ LZ = "LZ"
MSC = "MSC"
MSI = "MSI"
MSP = "MSP"
@@ -41,10 +48,16 @@ class ForbiddenFileType(StrEnum):
REG = "REG"
RGS = "RGS"
RUN = "RUN"
+ S7Z = "S7Z"
SCR = "SCR"
SCT = "SCT"
SHB = "SHB"
SHS = "SHS"
+ TAR = "TAR"
+ TBZ2 = "TBZ2"
+ TGZ = "TGZ"
+ TLZ = "TLZ"
+ TX7 = "TX7"
U3P = "U3P"
VB = "VB"
VBE = "VBE"
@@ -54,6 +67,11 @@ class ForbiddenFileType(StrEnum):
WS = "WS"
WSF = "WSF"
WSH = "WSH"
+ X7 = "X7"
+ Z = "Z"
+ ZIPX = "ZIPX"
+ ZST = "ZST"
+ ZZ = "ZZ"
_7Z = "7Z"
diff --git a/lambdas/models/upload_file_config.py b/lambdas/models/upload_file_config.py
index 2abb362fd6..357cff4b57 100644
--- a/lambdas/models/upload_file_config.py
+++ b/lambdas/models/upload_file_config.py
@@ -19,6 +19,7 @@ class DocumentConfig(BaseModel):
multifile_zipped: bool
multifile_review: bool
can_be_discarded: bool
+ single_file_only: bool
stitched: bool
accepted_file_types: List[str]
content: List[Any]
diff --git a/lambdas/services/create_document_reference_service.py b/lambdas/services/create_document_reference_service.py
index 73cd7013ce..28d45813f0 100644
--- a/lambdas/services/create_document_reference_service.py
+++ b/lambdas/services/create_document_reference_service.py
@@ -15,8 +15,9 @@
)
from utils import upload_file_configs
from utils.audit_logging_setup import LoggingService
-from utils.common_query_filters import NotDeleted
+from utils.common_query_filters import get_document_type_filter
from utils.constants.ssm import UPLOAD_PILOT_ODS_ALLOWED_LIST
+from utils.dynamo_query_filter_builder import DynamoQueryFilterBuilder
from utils.exceptions import (
ConfigNotFoundException,
InvalidNhsNumberException,
@@ -60,14 +61,6 @@ def create_document_reference_request(
url_responses = {}
upload_request_documents = self.parse_documents_list(documents_list)
- if any(
- document.doc_type == SupportedDocumentTypes.LG
- for document in upload_request_documents
- ):
- self.check_existing_lloyd_george_records_and_remove_failed_upload(
- nhs_number
- )
-
try:
user_ods_code = extract_ods_code_from_request_context()
@@ -80,12 +73,21 @@ def create_document_reference_request(
for validated_doc in upload_request_documents:
snomed_code = validated_doc.doc_type
+ config = upload_file_configs.get_config_by_snomed_code(
+ snomed_code
+ )
+
+ if config.single_file_only:
+ self.check_existing_records_and_remove_failed_upload(
+ nhs_number,
+ snomed_code
+ )
document_reference = self.create_document_reference(
nhs_number, user_ods_code, validated_doc, snomed_code
)
- self.validate_document_file_type(validated_doc, snomed_code)
+ self.validate_document_file_type(validated_doc, config)
upload_document_names.append(validated_doc.file_name)
@@ -115,6 +117,7 @@ def create_document_reference_request(
InvalidNhsNumberException,
LGInvalidFilesException,
PdsTooManyRequestsException,
+ ConfigNotFoundException,
) as e:
logger.error(
f"{LambdaError.DocRefInvalidFiles.to_str()} :{str(e)}",
@@ -122,12 +125,8 @@ def create_document_reference_request(
)
raise DocumentRefException(400, LambdaError.DocRefInvalidFiles)
- def validate_document_file_type(self, validated_doc, snomed_code):
- accepted_file_types = self.get_accepted_file_types(
- snomed_code, upload_file_configs
- )
-
- if not is_file_type_allowed(validated_doc.file_name, accepted_file_types):
+ def validate_document_file_type(self, validated_doc, document_config):
+ if not is_file_type_allowed(validated_doc.file_name, document_config.accepted_file_types):
raise LGInvalidFilesException(
f"Unsupported file type for file: {validated_doc.file_name}"
)
@@ -159,18 +158,6 @@ def validate_patient_user_ods_codes_match(self, user_ods_code, patient_ods_code)
)
raise DocumentRefException(401, LambdaError.DocRefUnauthorizedOdsCode)
- def get_accepted_file_types(self, snomed_code, upload_file_configs):
- try:
- return upload_file_configs.get_config_by_snomed_code(
- snomed_code
- ).accepted_file_types
- except ConfigNotFoundException:
- logger.error(
- f"{LambdaError.DocRefInvalidType.to_str()}",
- {"Result": UPLOAD_REFERENCE_FAILED_MESSAGE},
- )
- raise DocumentRefException(400, LambdaError.DocRefInvalidType)
-
def build_doc_ref_info(
self, validated_doc, nhs_number, snomed_code, user_ods_code
) -> DocumentReferenceInfo:
@@ -247,16 +234,19 @@ def create_document_reference(
)
return document_reference
- def check_existing_lloyd_george_records_and_remove_failed_upload(
+ def check_existing_records_and_remove_failed_upload(
self,
nhs_number: str,
+ doc_type: str,
) -> None:
logger.info("Looking for previous records for this patient...")
+ query_filter = get_document_type_filter(DynamoQueryFilterBuilder(), doc_type)
+
previous_records = self.post_fhir_doc_ref_service.document_service.fetch_available_document_references_by_type(
nhs_number=nhs_number,
- doc_type=SupportedDocumentTypes.LG,
- query_filter=NotDeleted,
+ doc_type=SupportedDocumentTypes(doc_type),
+ query_filter=query_filter,
)
if not previous_records:
logger.info(
diff --git a/lambdas/tests/unit/enums/test_metadata_field_names.py b/lambdas/tests/unit/enums/test_metadata_field_names.py
index 382854c89e..b4c1708130 100755
--- a/lambdas/tests/unit/enums/test_metadata_field_names.py
+++ b/lambdas/tests/unit/enums/test_metadata_field_names.py
@@ -8,7 +8,7 @@ def test_can_get_one_field_name():
def test_returns_all_as_list():
subject = DocumentReferenceMetadataFields.list()
- assert len(subject) == 16
+ assert len(subject) == 17
assert DocumentReferenceMetadataFields.ID.value in subject
assert DocumentReferenceMetadataFields.CONTENT_TYPE.value in subject
assert DocumentReferenceMetadataFields.CREATED.value in subject
@@ -25,3 +25,4 @@ def test_returns_all_as_list():
assert DocumentReferenceMetadataFields.DOCUMENT_SCAN_CREATION.value in subject
assert DocumentReferenceMetadataFields.CUSTODIAN.value in subject
assert DocumentReferenceMetadataFields.FILE_SIZE.value in subject
+ assert DocumentReferenceMetadataFields.DOCUMENT_SNOMED_CODE_TYPE.value in subject
\ No newline at end of file
diff --git a/lambdas/tests/unit/helpers/data/test_documents.py b/lambdas/tests/unit/helpers/data/test_documents.py
index 4f7d98fa55..6035c1e474 100644
--- a/lambdas/tests/unit/helpers/data/test_documents.py
+++ b/lambdas/tests/unit/helpers/data/test_documents.py
@@ -56,15 +56,18 @@ def create_test_lloyd_george_doc_store_refs(
refs[0].s3_file_key = f"{TEST_NHS_NUMBER}/test-key-1"
refs[0].file_location = f"s3://{MOCK_LG_BUCKET}/{TEST_NHS_NUMBER}/test-key-1"
refs[0].s3_bucket_name = MOCK_LG_BUCKET
+ refs[0].document_snomed_code_type = SnomedCodes.LLOYD_GEORGE.value.code
refs[1].file_name = filename_2
refs[1].s3_file_key = f"{TEST_NHS_NUMBER}/test-key-2"
refs[1].file_location = f"s3://{MOCK_LG_BUCKET}/{TEST_NHS_NUMBER}/test-key-2"
refs[1].s3_bucket_name = MOCK_LG_BUCKET
+ refs[1].document_snomed_code_type = SnomedCodes.LLOYD_GEORGE.value.code
refs[2].file_name = filename_3
refs[2].s3_file_key = f"{TEST_NHS_NUMBER}/test-key-3"
refs[2].file_location = f"s3://{MOCK_LG_BUCKET}/{TEST_NHS_NUMBER}/test-key-3"
refs[2].s3_bucket_name = MOCK_LG_BUCKET
-
+ refs[2].document_snomed_code_type = SnomedCodes.LLOYD_GEORGE.value.code
+
if override:
refs = [doc_ref.model_copy(update=override) for doc_ref in refs]
return refs
diff --git a/lambdas/tests/unit/services/test_create_document_reference_service.py b/lambdas/tests/unit/services/test_create_document_reference_service.py
index 21abadf58c..8a1332e037 100644
--- a/lambdas/tests/unit/services/test_create_document_reference_service.py
+++ b/lambdas/tests/unit/services/test_create_document_reference_service.py
@@ -2,7 +2,9 @@
from datetime import datetime
import pytest
+from enums.dynamo_filter import AttributeOperator
from enums.lambda_error import LambdaError
+from enums.metadata_field_names import DocumentReferenceMetadataFields
from enums.snomed_codes import SnomedCodes
from freezegun import freeze_time
from models.document_reference import DocumentReference, UploadRequestDocument
@@ -19,7 +21,9 @@
from tests.unit.helpers.data.test_documents import (
create_test_lloyd_george_doc_store_refs,
)
+from utils.common_query_filters import NotDeleted
from utils.constants.ssm import UPLOAD_PILOT_ODS_ALLOWED_LIST
+from utils.dynamo_query_filter_builder import DynamoQueryFilterBuilder
from utils.exceptions import PatientNotFoundException
from utils.lambda_exceptions import DocumentRefException
from utils.lloyd_george_validator import LGInvalidFilesException
@@ -120,12 +124,12 @@ def mock_remove_records(mock_create_doc_ref_service, mocker):
@pytest.fixture()
-def mock_check_existing_lloyd_george_records_and_remove_failed_upload(
+def mock_check_existing_records_and_remove_failed_upload(
mock_create_doc_ref_service, mocker
):
yield mocker.patch.object(
mock_create_doc_ref_service,
- "check_existing_lloyd_george_records_and_remove_failed_upload",
+ "check_existing_records_and_remove_failed_upload",
)
@@ -197,7 +201,7 @@ def test_create_document_reference_request_with_lg_list_happy_path(
mock_process_fhir_document_reference,
mock_getting_patient_info_from_pds,
mock_get_allowed_list_of_ods_codes_for_upload_pilot,
- mock_check_existing_lloyd_george_records_and_remove_failed_upload,
+ mock_check_existing_records_and_remove_failed_upload,
mock_fetch_available_document_references_by_type,
mock_check_for_duplicate_files,
):
@@ -216,8 +220,8 @@ def test_create_document_reference_request_with_lg_list_happy_path(
}
assert url_references == expected_response
- mock_check_existing_lloyd_george_records_and_remove_failed_upload.assert_called_with(
- TEST_NHS_NUMBER
+ mock_check_existing_records_and_remove_failed_upload.assert_called_with(
+ TEST_NHS_NUMBER, LG_FILE_LIST[0]["docType"]
)
mock_check_for_duplicate_files.assert_called_once()
@@ -231,7 +235,7 @@ def test_create_document_reference_request_raise_error_when_invalid_lg(
mock_check_for_duplicate_files,
mock_getting_patient_info_from_pds,
mock_get_allowed_list_of_ods_codes_for_upload_pilot,
- mock_check_existing_lloyd_george_records_and_remove_failed_upload,
+ mock_check_existing_records_and_remove_failed_upload,
):
document_references = []
side_effects = []
@@ -329,7 +333,7 @@ def test_cdr_non_pdf_file_raises_exception(
mock_check_for_duplicate_files,
mock_getting_patient_info_from_pds,
mock_get_allowed_list_of_ods_codes_for_upload_pilot,
- mock_check_existing_lloyd_george_records_and_remove_failed_upload,
+ mock_check_existing_records_and_remove_failed_upload,
):
mock_check_for_duplicate_files.side_effect = LGInvalidFilesException
mock_get_allowed_list_of_ods_codes_for_upload_pilot.return_value = [
@@ -399,7 +403,7 @@ def test_create_document_reference_request_lg_upload_throw_lambda_error_if_got_a
assert e.value == DocumentRefException(422, LambdaError.DocRefRecordAlreadyInPlace)
-def test_check_existing_lloyd_george_records_remove_previous_failed_upload_and_continue(
+def test_check_existing_records_remove_previous_failed_upload_and_continue(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
mock_fetch_available_document_references_by_type,
@@ -415,8 +419,8 @@ def test_check_existing_lloyd_george_records_remove_previous_failed_upload_and_c
mock_create_doc_ref_service.stop_if_all_records_uploaded = mocker.MagicMock()
mock_create_doc_ref_service.stop_if_upload_is_in_process = mocker.MagicMock()
- mock_create_doc_ref_service.check_existing_lloyd_george_records_and_remove_failed_upload(
- TEST_NHS_NUMBER
+ mock_create_doc_ref_service.check_existing_records_and_remove_failed_upload(
+ TEST_NHS_NUMBER, mock_doc_refs_of_failed_upload[0].document_snomed_code_type
)
mock_remove_records.assert_called_with(
MOCK_LG_TABLE_NAME, mock_doc_refs_of_failed_upload
@@ -505,7 +509,7 @@ def test_prepare_doc_object_lg_happy_path(
)
-def test_check_existing_lloyd_george_records_does_nothing_if_no_record_exist(
+def test_check_existing_records_does_nothing_if_no_record_exist(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
mock_fetch_available_document_references_by_type,
@@ -515,8 +519,9 @@ def test_check_existing_lloyd_george_records_does_nothing_if_no_record_exist(
mock_fetch_available_document_references_by_type.return_value = []
assert (
- mock_create_doc_ref_service.check_existing_lloyd_george_records_and_remove_failed_upload(
- TEST_NHS_NUMBER
+ mock_create_doc_ref_service.check_existing_records_and_remove_failed_upload(
+ TEST_NHS_NUMBER,
+ SupportedDocumentTypes.LG
)
is None
)
@@ -524,7 +529,7 @@ def test_check_existing_lloyd_george_records_does_nothing_if_no_record_exist(
@freeze_time("2023-10-30T10:25:00")
-def test_check_existing_lloyd_george_records_throw_error_if_upload_in_progress(
+def test_check_existing_records_throw_error_if_upload_in_progress(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
mock_fetch_available_document_references_by_type,
@@ -542,8 +547,9 @@ def test_check_existing_lloyd_george_records_throw_error_if_upload_in_progress(
)
with pytest.raises(Exception) as e:
- mock_create_doc_ref_service.check_existing_lloyd_george_records_and_remove_failed_upload(
- TEST_NHS_NUMBER
+ mock_create_doc_ref_service.check_existing_records_and_remove_failed_upload(
+ TEST_NHS_NUMBER,
+ SupportedDocumentTypes.LG
)
ex = e.value
assert isinstance(ex, DocumentRefException)
@@ -553,7 +559,7 @@ def test_check_existing_lloyd_george_records_throw_error_if_upload_in_progress(
mock_remove_records.assert_not_called()
-def test_check_existing_lloyd_george_records_throw_error_if_got_a_full_set_of_uploaded_record(
+def test_check_existing_records_throw_error_if_got_a_full_set_of_uploaded_record(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
mock_fetch_available_document_references_by_type,
@@ -564,8 +570,9 @@ def test_check_existing_lloyd_george_records_throw_error_if_got_a_full_set_of_up
)
with pytest.raises(Exception) as e:
- mock_create_doc_ref_service.check_existing_lloyd_george_records_and_remove_failed_upload(
- TEST_NHS_NUMBER
+ mock_create_doc_ref_service.check_existing_records_and_remove_failed_upload(
+ TEST_NHS_NUMBER,
+ SupportedDocumentTypes.LG
)
ex = e.value
@@ -648,7 +655,7 @@ def test_patient_ods_does_not_match_user_ods_and_raises_exception(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
mock_create_document_reference,
- mock_check_existing_lloyd_george_records_and_remove_failed_upload,
+ mock_check_existing_records_and_remove_failed_upload,
):
with pytest.raises(DocumentRefException) as exc_info:
@@ -667,10 +674,10 @@ def test_patient_ods_does_not_match_user_ods_and_raises_exception(
)
-def test_unable_to_find_config_reiases_exception(
+def test_unable_to_find_config_raises_exception(
mock_fhir_doc_ref_base_service,
mock_create_doc_ref_service,
- mock_check_existing_lloyd_george_records_and_remove_failed_upload,
+ mock_check_existing_records_and_remove_failed_upload,
mock_getting_patient_info_from_pds,
mock_get_allowed_list_of_ods_codes_for_upload_pilot,
mock_process_fhir_document_reference,
@@ -689,7 +696,36 @@ def test_unable_to_find_config_reiases_exception(
assert exception.status_code == 400
assert (
exception.message
- == "Failed to parse document upload request data due to invalid document type"
+ == "Invalid files or id"
)
mock_process_fhir_document_reference.assert_not_called()
+
+def test_check_existing_records_fetches_previous_records_for_doc_type(
+ mock_fhir_doc_ref_base_service,
+ mock_create_doc_ref_service,
+ mock_fetch_available_document_references_by_type,
+ mock_remove_records,
+ mocker
+):
+ doc_type = SupportedDocumentTypes.LG
+
+ expected_query_filter = NotDeleted & DynamoQueryFilterBuilder().add_condition(
+ DocumentReferenceMetadataFields.DOCUMENT_SNOMED_CODE_TYPE,
+ AttributeOperator.EQUAL,
+ doc_type
+ ).build()
+ mocker.patch(
+ "services.create_document_reference_service.get_document_type_filter"
+ ).return_value = expected_query_filter
+
+ mock_create_doc_ref_service.check_existing_records_and_remove_failed_upload(
+ TEST_NHS_NUMBER,
+ doc_type
+ )
+
+ mock_fetch_available_document_references_by_type.assert_called_with(
+ nhs_number=TEST_NHS_NUMBER,
+ doc_type=doc_type,
+ query_filter=expected_query_filter
+ )
\ No newline at end of file
diff --git a/lambdas/utils/dynamo_utils.py b/lambdas/utils/dynamo_utils.py
index 336de29101..8cc40be3fb 100644
--- a/lambdas/utils/dynamo_utils.py
+++ b/lambdas/utils/dynamo_utils.py
@@ -348,6 +348,7 @@ def __init__(self):
SnomedCodes.LLOYD_GEORGE.value.code: self.lg_dynamo_table,
SnomedCodes.EHR.value.code: self.lg_dynamo_table,
SnomedCodes.EHR_ATTACHMENTS.value.code: self.lg_dynamo_table,
+ SnomedCodes.LETTERS_AND_DOCUMENTS.value.code: self.lg_dynamo_table,
SnomedCodes.PATIENT_DATA.value.code: self.core_dynamo_table,
}
diff --git a/lambdas/utils/upload_file_configs.py b/lambdas/utils/upload_file_configs.py
index 35ae33d18c..14809812ae 100644
--- a/lambdas/utils/upload_file_configs.py
+++ b/lambdas/utils/upload_file_configs.py
@@ -13,6 +13,7 @@
multifile_zipped=False,
multifile_review=True,
can_be_discarded=True,
+ single_file_only=True,
stitched=True,
accepted_file_types=["PDF"],
content=[],
@@ -27,6 +28,7 @@
multifile_zipped=False,
multifile_review=True,
can_be_discarded=True,
+ single_file_only=False,
stitched=True,
accepted_file_types=["PDF"],
content=[],
@@ -41,15 +43,32 @@
multifile_zipped=False,
multifile_review=True,
can_be_discarded=True,
+ single_file_only=False,
stitched=True,
accepted_file_types=["ZIP"],
content=[],
)
+LETTERS_AND_DOCUMENTS = DocumentConfig(
+ snomed_code=SnomedCodes.LETTERS_AND_DOCUMENTS.value.code,
+ display_name="Letters and Documents",
+ can_be_updated=False,
+ associated_snomed="",
+ multifile_upload=True,
+ multifile_zipped=False,
+ multifile_review=False,
+ can_be_discarded=True,
+ single_file_only=False,
+ stitched=False,
+ accepted_file_types=[],
+ content=[],
+)
+
ALL_CONFIGS = [
LLOYD_GEORGE,
ELECTRONIC_HEALTH_RECORD,
ATTACHMENTS,
+ LETTERS_AND_DOCUMENTS,
]
CONFIG_BY_SNOMED: Dict[str, DocumentConfig] = {