diff --git a/Jenkinsfile_CNP b/Jenkinsfile_CNP index 2e6383af623..163e450593d 100644 --- a/Jenkinsfile_CNP +++ b/Jenkinsfile_CNP @@ -11,7 +11,7 @@ def camundaBranch = "master" def dmnBranch = "master" def waStandaloneBranch = "master" def ccdBranch = "master" -def generalappCCDBranch = "master" +def generalappCCDBranch = "CIV-14228-GA-Upload-additional-documents" AppPipelineConfig pipelineConf static Map secret(String secretName, String envVariable) { diff --git a/src/main/common/models/gaEvents/applicationEvent.ts b/src/main/common/models/gaEvents/applicationEvent.ts index ffbc23ade91..e523ecc5057 100644 --- a/src/main/common/models/gaEvents/applicationEvent.ts +++ b/src/main/common/models/gaEvents/applicationEvent.ts @@ -1,3 +1,4 @@ export enum ApplicationEvent { // TODO: Add events here for use by GA client + UPLOAD_ADDL_DOCUMENTS = 'UPLOAD_ADDL_DOCUMENTS', } diff --git a/src/main/common/models/gaEvents/eventDto.ts b/src/main/common/models/gaEvents/eventDto.ts index dbeab10ff25..6281ca82f80 100644 --- a/src/main/common/models/gaEvents/eventDto.ts +++ b/src/main/common/models/gaEvents/eventDto.ts @@ -24,15 +24,31 @@ export interface EventDto { } export interface CCDGeneralApplication extends ClaimUpdate { - generalAppType: CcdGeneralApplicationTypes; - generalAppRespondentAgreement: CcdGeneralApplicationRespondentAgreement; - generalAppInformOtherParty: CcdGeneralApplicationInformOtherParty; - generalAppAskForCosts: YesNoUpperCamelCase; - generalAppDetailsOfOrder: string; - generalAppReasonsOfOrder: string; - generalAppEvidenceDocument: CcdGeneralApplicationEvidenceDocument[]; - generalAppHearingDetails: CcdGeneralApplicationHearingDetails; - generalAppStatementOfTruth: CcdGeneralApplicationStatementOfTruth; - generalAppHelpWithFees: CCDHelpWithFees; + generalAppType?: CcdGeneralApplicationTypes; + generalAppRespondentAgreement?: CcdGeneralApplicationRespondentAgreement; + generalAppInformOtherParty?: CcdGeneralApplicationInformOtherParty; + generalAppAskForCosts?: YesNoUpperCamelCase; + generalAppDetailsOfOrder?: string; + generalAppReasonsOfOrder?: string; + generalAppEvidenceDocument?: CcdGeneralApplicationEvidenceDocument[]; + generalAppHearingDetails?: CcdGeneralApplicationHearingDetails; + generalAppStatementOfTruth?: CcdGeneralApplicationStatementOfTruth; + generalAppHelpWithFees?: CCDHelpWithFees; caseLink?: CaseLink; + uploadDocument?: AdditionalDocuments[] +} +interface DocumentDetails { + document_url: string; + document_binary_url: string; + document_filename: string; +} + +interface AdditionDocDetails { + typeOfDocument: string, + documentUpload: DocumentDetails +} + +export interface AdditionalDocuments { + id: string; + value: AdditionDocDetails } diff --git a/src/main/common/models/generalApplication/GeneralApplication.ts b/src/main/common/models/generalApplication/GeneralApplication.ts index cf86ba93460..81e95caf01e 100644 --- a/src/main/common/models/generalApplication/GeneralApplication.ts +++ b/src/main/common/models/generalApplication/GeneralApplication.ts @@ -14,6 +14,7 @@ import {GaResponse} from 'models/generalApplication/response/gaResponse'; import {GaHelpWithFees} from 'models/generalApplication/gaHelpWithFees'; import {PaymentInformation} from 'models/feePayment/paymentInformation'; import {CaseLink} from 'models/generalApplication/CaseLink'; +import { UploadAdditionalDocument } from './UploadAdditionalDocument'; export class GeneralApplication { @@ -36,6 +37,7 @@ export class GeneralApplication { helpWithFees?: GaHelpWithFees; applicationFeePaymentDetails : PaymentInformation; caseLink?: CaseLink; + uploadAdditionalDocuments?: UploadAdditionalDocument[] = []; constructor( applicationType?: ApplicationType, diff --git a/src/main/common/models/generalApplication/UploadAdditionalDocument.ts b/src/main/common/models/generalApplication/UploadAdditionalDocument.ts new file mode 100644 index 00000000000..2529f9a1780 --- /dev/null +++ b/src/main/common/models/generalApplication/UploadAdditionalDocument.ts @@ -0,0 +1,19 @@ +import { IsNotEmpty, ValidateIf, ValidateNested } from 'class-validator'; +import { FileUpload } from '../caseProgression/uploadDocumentsUserForm'; +import { CaseDocument } from '../document/caseDocument'; + +export class UploadAdditionalDocument { + @ValidateNested() + @ValidateIf((object) => object.caseDocument === undefined || object.caseDocument === null || object.caseDocument === '') + @IsNotEmpty({ message: 'ERRORS.GENERAL_APPLICATION.UPLOAD_FILE_MESSAGE' }) + fileUpload: FileUpload; + caseDocument: CaseDocument; + + @IsNotEmpty({ message: 'ERRORS.GENERAL_APPLICATION.TYPE_OF_DOC' }) + typeOfDocument: string; + + constructor(fileUpload?: FileUpload) { + this.fileUpload = fileUpload; + } + +} \ No newline at end of file diff --git a/src/main/common/utils/urlFormatter.ts b/src/main/common/utils/urlFormatter.ts index bb4416f0e33..bb71e5dab9c 100644 --- a/src/main/common/utils/urlFormatter.ts +++ b/src/main/common/utils/urlFormatter.ts @@ -4,6 +4,10 @@ export function constructResponseUrlWithIdParams(id: string, path: string): stri return path.replace(/(:id)/i, id); } +export function constructResponseUrlWithIdAndAppIdParams(id: string, appId: string, path: string): string { + return path.replace(/(:id)/i, id).replace(/(:appId)/i, appId); +} + export function constructUrlWithNotEligibleReason(path: string, reason: NotEligibleReason): string { return `${path}?reason=${reason}`; } diff --git a/src/main/modules/i18n/locales/cy.json b/src/main/modules/i18n/locales/cy.json index faad3a78cda..95b5cfc3d86 100644 --- a/src/main/modules/i18n/locales/cy.json +++ b/src/main/modules/i18n/locales/cy.json @@ -2575,6 +2575,16 @@ "WHY_NOT_ACCEPT": "Why do you not accept the defendant's offer?", "WHY_NOT_ACCEPT_2": "Why you do not accept the defendant's offer?" }, + "ADDITIONAL_DOCUMENTS": { + "ADDITIONAL_DOCUMENTS_CAPTION": "Upload additional documents", + "ADDITIONAL_DOCUMENTS_ROW1": "You should only upload documents that are relevant to your application, for example if the judge has ordered a hearing and instructed you to provide documents ahead of this.", + "ADDITIONAL_DOCUMENTS_ROW2": "Before you upload the document you will need to describe the type of document you are uploading, for example, Bundle, witness statement or costs schedule.", + "JUDGE_WILL_REVIEW": "A judge will review the additional documents that you’ve uploaded. You’ll receive an update with their decision or next steps.", + "TYPE_OF_DOCUMENT_HINT": "For example, contract, invoice receipt, email, text message, photo, social media message", + "TYPE_OF_DOCUMENT": "Type of document", + "UPLOAD_DOCUMENTS_TITLE": "Upload documents", + "UPLOADED_ADDITIONAL_DOCS": "You've uploaded additional documents" + }, "AGREE_TO_ORDER": { "MADE_AWARE": "If you have not been made aware of this application by the other parties, select 'No'.", "RESPOND_TO": "Respond to an application to {{applicationType}}", @@ -4952,7 +4962,9 @@ "NEED_TO_TELL": "You need to tell us if you agree that the court should make the order requested by the other party. Choose option Yes or No", "EXPLAIN_WHY_DISAGREE_APPLICATION": "You need to explain why you disagree with the application" }, + "TYPE_OF_DOC": "You need to tell us what type of document you are uploading.", "UPLOAD_FILE_MESSAGE": "You need to choose a file before clicking 'Upload file'", + "UPLOAD_ONE_FILE": "You need to upload at least one file.", "WANT_TO_ADD_ANOTHER_APPLICATION": "You need to tell us if you want to add another application. Choose option: Yes or No", "WANT_TO_UPLOAD_DOCUMENTS_YES_NO_SELECTION": "You need to tell us if you want to upload documents to support your application. Choose option: Yes or No", "WHY_PREFER_THIS_HEARING_TYPE": "You need to tell us why you would prefer this type of hearing", diff --git a/src/main/modules/i18n/locales/en.json b/src/main/modules/i18n/locales/en.json index 11a0ed05139..2cd3e70bfff 100644 --- a/src/main/modules/i18n/locales/en.json +++ b/src/main/modules/i18n/locales/en.json @@ -2575,6 +2575,16 @@ "WHY_NOT_ACCEPT": "Why do you not accept the defendant's offer?", "WHY_NOT_ACCEPT_2": "Why you do not accept the defendant's offer?" }, + "ADDITIONAL_DOCUMENTS": { + "ADDITIONAL_DOCUMENTS_CAPTION": "Upload additional documents", + "ADDITIONAL_DOCUMENTS_ROW1": "You should only upload documents that are relevant to your application, for example if the judge has ordered a hearing and instructed you to provide documents ahead of this.", + "ADDITIONAL_DOCUMENTS_ROW2": "Before you upload the document you will need to describe the type of document you are uploading, for example, Bundle, witness statement or costs schedule.", + "JUDGE_WILL_REVIEW": "A judge will review the additional documents that you’ve uploaded. You’ll receive an update with their decision or next steps.", + "TYPE_OF_DOCUMENT_HINT": "For example, contract, invoice receipt, email, text message, photo, social media message", + "TYPE_OF_DOCUMENT": "Type of document", + "UPLOAD_DOCUMENTS_TITLE": "Upload documents", + "UPLOADED_ADDITIONAL_DOCS": "You've uploaded additional documents" + }, "AGREE_TO_ORDER": { "MADE_AWARE": "If you have not been made aware of this application by the other parties, select 'No'.", "RESPOND_TO": "Respond to an application to {{applicationType}}", @@ -4952,7 +4962,9 @@ "NEED_TO_TELL": "You need to tell us if you agree that the court should make the order requested by the other party. Choose option Yes or No", "EXPLAIN_WHY_DISAGREE_APPLICATION": "You need to explain why you disagree with the application" }, + "TYPE_OF_DOC": "You need to tell us what type of document you are uploading.", "UPLOAD_FILE_MESSAGE": "You need to choose a file before clicking 'Upload file'", + "UPLOAD_ONE_FILE": "You need to upload at least one file.", "WANT_TO_ADD_ANOTHER_APPLICATION": "You need to tell us if you want to add another application. Choose option: Yes or No", "WANT_TO_UPLOAD_DOCUMENTS_YES_NO_SELECTION": "You need to tell us if you want to upload documents to support your application. Choose option: Yes or No", "WHY_PREFER_THIS_HEARING_TYPE": "You need to tell us why you would prefer this type of hearing", diff --git a/src/main/routes/features/generalApplication/additionalDocuments/checkAnswersController.ts b/src/main/routes/features/generalApplication/additionalDocuments/checkAnswersController.ts new file mode 100644 index 00000000000..7c3c0f9187b --- /dev/null +++ b/src/main/routes/features/generalApplication/additionalDocuments/checkAnswersController.ts @@ -0,0 +1,46 @@ +import { ApplicationEvent } from 'common/models/gaEvents/applicationEvent'; +import { AppRequest } from 'common/models/AppRequest'; +import { buildSummarySectionForAdditionalDoc, getClaimDetailsById, prepareCCDData } from 'services/features/generalApplication/additionalDocumentService'; +import { caseNumberPrettify } from 'common/utils/stringUtils'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL } from 'routes/urls'; +import { GaServiceClient } from 'client/gaServiceClient'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; +import { NextFunction, RequestHandler, Response, Router } from 'express'; +import config from 'config'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; + +const gaAdditionalDocCheckAnswerController = Router(); +const viewPath = 'features/generalApplication/additionalDocuments/check-answers'; +const generalAppApiBaseUrl = config.get('services.generalApplication.url'); +const gaServiceClient: GaServiceClient = new GaServiceClient(generalAppApiBaseUrl); + +gaAdditionalDocCheckAnswerController.get(GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, (async (req: AppRequest, res: Response, next: NextFunction) => { + try { + const { appId, id: claimId } = req.params; + const claimIdPrettified = caseNumberPrettify(claimId); + const claim = await getClaimDetailsById(req); + const cancelUrl = await getCancelUrl(claimId, claim); + const backLinkUrl = constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL); + const summaryRows = buildSummarySectionForAdditionalDoc(claim.generalApplication.uploadAdditionalDocuments, claimId, appId); + res.render(viewPath, { backLinkUrl, cancelUrl, claimIdPrettified, claim, summaryRows }); + } catch (error) { + next(error); + } +}) as RequestHandler); + +gaAdditionalDocCheckAnswerController.post(GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, (async (req: AppRequest, res: Response, next: NextFunction) => { + try { + const { appId, id: claimId } = req.params; + const claim = await getClaimDetailsById(req); + const uploadedDocuments = prepareCCDData(claim.generalApplication.uploadAdditionalDocuments); + const generalApplication = { + uploadDocument: uploadedDocuments, + }; + await gaServiceClient.submitEvent(ApplicationEvent.UPLOAD_ADDL_DOCUMENTS, appId, generalApplication as unknown, req); + res.redirect(constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL)); + } catch (error) { + next(error); + } +}) as RequestHandler); + +export default gaAdditionalDocCheckAnswerController; \ No newline at end of file diff --git a/src/main/routes/features/generalApplication/additionalDocuments/submittedController.ts b/src/main/routes/features/generalApplication/additionalDocuments/submittedController.ts new file mode 100644 index 00000000000..cde5de69d24 --- /dev/null +++ b/src/main/routes/features/generalApplication/additionalDocuments/submittedController.ts @@ -0,0 +1,29 @@ +import { AppRequest } from 'common/models/AppRequest'; +import { NextFunction, RequestHandler, Response, Router } from 'express'; +import { deleteDraftClaimFromStore, generateRedisKey } from 'modules/draft-store/draftStoreService'; +import { getClaimById } from 'modules/utilityService'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL } from 'routes/urls'; +import { getContentForBody, getContentForCloseButton, getContentForPanel } from 'services/features/generalApplication/additionalDocumentService'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; + +const additionalDocSubmittedController = Router(); +const viewPath = 'features/generalApplication/additionalDocuments/submitted'; + +additionalDocSubmittedController.get(GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL, (async (req: AppRequest, res: Response, next: NextFunction) => { + try { + const lng = req.query.lang ? req.query.lang : req.cookies.lang; + const { id: claimId } = req.params; + const claim = await getClaimById(claimId, req, true); + const redisKey = generateRedisKey(req); + await deleteDraftClaimFromStore(redisKey); + res.render(viewPath, { + gaPaymentSuccessfulPanel: getContentForPanel(lng), + gaPaymentSuccessfulBody: getContentForBody(lng), + gaPaymentSuccessfulButton: getContentForCloseButton(await getCancelUrl(claimId, claim)), + }); + } catch (err) { + next(err); + } +}) as RequestHandler); + +export default additionalDocSubmittedController; \ No newline at end of file diff --git a/src/main/routes/features/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.ts b/src/main/routes/features/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.ts new file mode 100644 index 00000000000..df3291c7e86 --- /dev/null +++ b/src/main/routes/features/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.ts @@ -0,0 +1,81 @@ +import { GenericForm } from 'common/form/models/genericForm'; +import { AppRequest } from 'common/models/AppRequest'; +import { NextFunction, RequestHandler, Response, Router } from 'express'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL, GA_VIEW_APPLICATION_URL } from 'routes/urls'; +import multer from 'multer'; +import { UploadAdditionalDocument } from 'common/models/generalApplication/UploadAdditionalDocument'; +import { generateRedisKey } from 'modules/draft-store/draftStoreService'; +import { constructResponseUrlWithIdAndAppIdParams, constructResponseUrlWithIdParams } from 'common/utils/urlFormatter'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; +import { getClaimDetailsById, getSummaryList, removeSelectedDocument, uploadSelectedFile } from 'services/features/generalApplication/additionalDocumentService'; + +const uploadAdditionalDocumentsController = Router(); + +const viewPath = 'features/generalApplication/additionalDocuments/upload-additional-documents'; +const fileSize = Infinity; +const upload = multer({ + limits: { + fileSize: fileSize, + }, +}); + +uploadAdditionalDocumentsController.get(GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL, (async (req: AppRequest, res: Response, next: NextFunction) => { + try { + const { appId:gaId, id } = req.params; + const uploadedDocument = new UploadAdditionalDocument(); + let form = new GenericForm(uploadedDocument); + const redisKey = generateRedisKey(req); + const claim = await getClaimDetailsById(req); + const gaDetails = claim.generalApplication; + + if (req.session?.fileUpload) { + const parsedData = JSON.parse(req?.session?.fileUpload); + form = new GenericForm(uploadedDocument, parsedData); + req.session.fileUpload = undefined; + } + if (req.query?.indexId) { + const index = req.query.indexId; + await removeSelectedDocument(redisKey, claim, Number(index) - 1); + } + const cancelUrl = await getCancelUrl(id, claim); + const backLinkUrl = `${constructResponseUrlWithIdParams(id, GA_VIEW_APPLICATION_URL)}?applicationId=${gaId}`; + const formattedSummary = getSummaryList(gaDetails.uploadAdditionalDocuments, id, gaId); + res.render(viewPath, { cancelUrl, backLinkUrl, form, formattedSummary }); + } catch (err) { + next(err); + } +}) as RequestHandler); + +uploadAdditionalDocumentsController.post(GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL, upload.single('selectedFile'), (async (req: AppRequest, res: Response, next: NextFunction) => { + try { + const { appId, id: claimId } = req.params; + const currentUrl = constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL); + const claim = await getClaimDetailsById(req); + const gaDetails = claim.generalApplication; + if (req.body.action === 'uploadButton') { + await uploadSelectedFile(req, claim); + return res.redirect(`${currentUrl}`); + } + if (gaDetails.uploadAdditionalDocuments.length === 0) { + const errors = [{ + target: { + fileUpload: '', + typeOfDocument: '', + }, + value: '', + property: '', + + constraints: { + isNotEmpty: 'ERRORS.GENERAL_APPLICATION.UPLOAD_ONE_FILE', + }, + }]; + req.session.fileUpload = JSON.stringify(errors); + return res.redirect(`${currentUrl}`); + } + res.redirect(constructResponseUrlWithIdAndAppIdParams(claimId, appId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)); + } catch (err) { + next(err); + } +})); + +export default uploadAdditionalDocumentsController; \ No newline at end of file diff --git a/src/main/routes/features/generalApplication/viewApplicationController.ts b/src/main/routes/features/generalApplication/viewApplicationController.ts index 8b735d17aa8..b72d6f9ef31 100644 --- a/src/main/routes/features/generalApplication/viewApplicationController.ts +++ b/src/main/routes/features/generalApplication/viewApplicationController.ts @@ -1,8 +1,9 @@ import {NextFunction, RequestHandler, Response, Router} from 'express'; -import {DASHBOARD_URL, GA_VIEW_APPLICATION_URL} from 'routes/urls'; +import { DASHBOARD_URL, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL, GA_VIEW_APPLICATION_URL } from 'routes/urls'; import {AppRequest} from 'common/models/AppRequest'; import {getApplicationSections} from 'services/features/generalApplication/viewApplication/viewApplicationService'; import {queryParamNumber} from 'common/utils/requestUtils'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; const viewApplicationController = Router(); const viewPath = 'features/generalApplication/view-applications'; @@ -15,7 +16,8 @@ viewApplicationController.get(GA_VIEW_APPLICATION_URL, (async (req: AppRequest, const lang = req.query.lang ? req.query.lang : req.cookies.lang; const summaryRows = await getApplicationSections(req, applicationId, lang); const pageTitle = 'PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.PAGE_TITLE'; - res.render(viewPath, {backLinkUrl, summaryRows, pageTitle, dashboardUrl: DASHBOARD_URL, applicationIndex }); + const additionalDocUrl = constructResponseUrlWithIdAndAppIdParams(req.params.id, applicationId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL); + res.render(viewPath, { backLinkUrl, summaryRows, additionalDocUrl, pageTitle, dashboardUrl: DASHBOARD_URL, applicationIndex }); } catch (error) { next(error); } diff --git a/src/main/routes/routes.ts b/src/main/routes/routes.ts index 4f7a0846d17..16656036130 100644 --- a/src/main/routes/routes.ts +++ b/src/main/routes/routes.ts @@ -384,6 +384,9 @@ import whyNotSubjectToFRCController from 'routes/features/directionsQuestionnaire/fixedRecoverableCosts/whyNotSubjectToFRCController'; import backController from 'routes/common/backController'; import applicationFeePaymentConfirmationController from './features/generalApplication/payment/applicationFeePaymentConfirmationController'; +import uploadAdditionalDocumentsController from './features/generalApplication/additionalDocuments/uploadAdditionalDocumentsController'; +import gaAdditionalDocCheckAnswerController from './features/generalApplication/additionalDocuments/checkAnswersController'; +import additionalDocSubmittedController from './features/generalApplication/additionalDocuments/submittedController'; import viewApplicationToRespondentController from 'routes/features/generalApplication/response/viewApplicationController'; import applicationSummaryController from './features/generalApplication/applicationSummaryController'; import requestForReviewCommentsController @@ -721,4 +724,7 @@ export default [ respondentHearingPreferenceController, respondentWantToUploadDocumentsController, respondentUploadEvidenceDocumentsController, + uploadAdditionalDocumentsController, + gaAdditionalDocCheckAnswerController, + additionalDocSubmittedController, ]; diff --git a/src/main/routes/urls.ts b/src/main/routes/urls.ts index 50a8c8e3445..39b463d6e20 100644 --- a/src/main/routes/urls.ts +++ b/src/main/routes/urls.ts @@ -368,6 +368,9 @@ export const FRC_BAND_AGREED_URL = `${DIRECTIONS_QUESTIONNAIRE_URL}/frc-band-agr export const ASSIGN_FRC_BAND_URL = `${DIRECTIONS_QUESTIONNAIRE_URL}/assign-complexity-band`; export const REASON_FOR_FRC_BAND_URL = `${DIRECTIONS_QUESTIONNAIRE_URL}/reason-for-complexity-band`; export const WHY_NOT_SUBJECT_TO_FRC_URL = `${DIRECTIONS_QUESTIONNAIRE_URL}/why-not-subject-to-frc`; +export const GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL = `${BASE_GENERAL_APPLICATION_URL}/:appId/upload-additional-documents`; +export const GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL = `${BASE_GENERAL_APPLICATION_URL}/:appId/upload-additional-documents/check-and-send`; +export const GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL = `${BASE_GENERAL_APPLICATION_URL}/:appId/upload-additional-documents/submitted`; export const GA_RESPONSE_VIEW_APPLICATION_URL = `${BASE_GENERAL_APPLICATION_RESPONSE_URL}/view-application`; export const GA_RESPONDENT_HEARING_PREFERENCE_URL = `${BASE_GENERAL_APPLICATION_RESPONSE_URL}/hearing-preference`; export const GA_RESPONDENT_WANT_TO_UPLOAD_DOCUMENT_URL = `${BASE_GENERAL_APPLICATION_RESPONSE_URL}/want-to-upload-document`; diff --git a/src/main/services/features/generalApplication/additionalDocumentService.ts b/src/main/services/features/generalApplication/additionalDocumentService.ts new file mode 100644 index 00000000000..e34f6ffb9b7 --- /dev/null +++ b/src/main/services/features/generalApplication/additionalDocumentService.ts @@ -0,0 +1,124 @@ +import { AppRequest } from 'common/models/AppRequest'; +import { Claim } from 'common/models/claim'; +import { UploadAdditionalDocument } from 'common/models/generalApplication/UploadAdditionalDocument'; +import { SummaryRow, summaryRow } from 'common/models/summaryList/summaryList'; +import { SummarySection, summarySection } from 'common/models/summaryList/summarySections'; +import { generateRedisKey, saveDraftClaim } from 'modules/draft-store/draftStoreService'; +import { TypeOfDocumentSectionMapper } from '../caseProgression/TypeOfDocumentSectionMapper'; +import { GenericForm } from 'common/form/models/genericForm'; +import { translateErrors } from './uploadEvidenceDocumentService'; +import { t } from 'i18next'; +import config from 'config'; +import { CivilServiceClient } from 'client/civilServiceClient'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL } from 'routes/urls'; +import { getClaimById } from 'modules/utilityService'; +import { GeneralApplication } from 'common/models/generalApplication/GeneralApplication'; +import { changeLabel } from 'common/utils/checkYourAnswer/changeButton'; +import { PaymentSuccessfulSectionBuilder } from '../claim/paymentSuccessfulSectionBuilder'; +import { getLng } from 'common/utils/languageToggleUtils'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; + +const { v4: uuIdv4 } = require('uuid'); +const { Logger } = require('@hmcts/nodejs-logging'); +const logger = Logger.getLogger('additionalDocumentService'); +const civilServiceApiBaseUrl = config.get('services.civilService.url'); +const civilServiceClientForDocRetrieve: CivilServiceClient = new CivilServiceClient(civilServiceApiBaseUrl, true); + +export const getSummaryList = (additionalDocumentsList: UploadAdditionalDocument[], claimId: string, gaId: string): SummarySection => { + let index = 0; + const formattedSummary = summarySection( + { + title: '', + summaryRows: [], + }); + additionalDocumentsList.forEach((uploadDocument: UploadAdditionalDocument) => { + index = index + 1; + formattedSummary.summaryList.rows.push(summaryRow('Type of document', uploadDocument.typeOfDocument)); + formattedSummary.summaryList.rows.push(summaryRow(uploadDocument.caseDocument.documentName, '', `${constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)}?indexId=${index}`, 'Remove document')); + }); + return formattedSummary; +}; + +export const removeSelectedDocument = async (redisKey: string, claim: Claim, index: number): Promise => { + try { + claim?.generalApplication?.uploadAdditionalDocuments?.splice(index, 1); + await saveDraftClaim(redisKey, claim); + } catch (error) { + logger.error(error); + throw error; + } +}; + +export const uploadSelectedFile = async (req: AppRequest, claim: Claim) => { + const uploadedDocument = new UploadAdditionalDocument(); + const fileUpload = TypeOfDocumentSectionMapper.mapToSingleFile(req); + uploadedDocument.fileUpload = fileUpload; + uploadedDocument.typeOfDocument = req.body.typeOfDocument; + const form = new GenericForm(uploadedDocument); + const uploadAdditionalDocuments = claim.generalApplication.uploadAdditionalDocuments; + form.validateSync(); + if (!form.hasErrors()) { + uploadedDocument.caseDocument = await civilServiceClientForDocRetrieve.uploadDocument(req, fileUpload); + uploadAdditionalDocuments.push(uploadedDocument); + await saveDraftClaim(generateRedisKey(req), claim); + } else { + const errors = translateErrors(form.getAllErrors(), t); + req.session.fileUpload = JSON.stringify(errors); + } +}; + +export const getClaimDetailsById = async (req: AppRequest): Promise => { + try { + const claim = await getClaimById(req.params.id, req, true); + const gaApplication = Object.assign(new GeneralApplication(), claim.generalApplication); + claim.generalApplication = gaApplication; + return claim; + } catch (error) { + logger.error(error); + throw error; + } +}; + +export const prepareCCDData = (uploadAdditionalDocuments: UploadAdditionalDocument[]) => { + return uploadAdditionalDocuments.map(doc => { + return { + id: uuIdv4(), + value: { + typeOfDocument: doc.typeOfDocument, + documentUpload: { + document_url: doc.caseDocument.documentLink.document_url, + document_binary_url: doc.caseDocument.documentLink.document_binary_url, + document_filename: doc.caseDocument.documentName, + }, + }, + }; + }); +}; + +export const buildSummarySectionForAdditionalDoc = (additionalDocumentsList: UploadAdditionalDocument[], claimId: string, gaId: string) => { + const rows: SummaryRow[] = []; + additionalDocumentsList.forEach(doc => { + rows.push(summaryRow('Type of document', doc.typeOfDocument)); + rows.push(summaryRow('Uploaded document', doc.caseDocument.documentName, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL.replace(':id', claimId).replace(':gaId', gaId), changeLabel('en'))); + }); + return rows; +}; + +export const getContentForPanel = (lng: string) => { + const panelBuilder = new PaymentSuccessfulSectionBuilder(); + panelBuilder.addPanelForConfirmation('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.UPLOADED_ADDITIONAL_DOCS', getLng(lng)); + return panelBuilder.build(); +}; + +export const getContentForBody = (lng: string) => { + const contentBuilder = new PaymentSuccessfulSectionBuilder(); + return contentBuilder.addTitle('PAGES.GENERAL_APPLICATION.GA_PAYMENT_SUCCESSFUL.WHAT_HAPPENS_NEXT', { lng: getLng(lng) }) + .addParagraph('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.JUDGE_WILL_REVIEW', { lng: getLng(lng) }) + .build(); +}; + +export const getContentForCloseButton = (redirectUrl: string) => { + return new PaymentSuccessfulSectionBuilder() + .addButton('COMMON.BUTTONS.CLOSE_AND_RETURN_TO_DASHBOARD', redirectUrl) + .build(); +}; \ No newline at end of file diff --git a/src/main/views/features/generalApplication/additionalDocuments/check-answers.njk b/src/main/views/features/generalApplication/additionalDocuments/check-answers.njk new file mode 100644 index 00000000000..c7a52e2d526 --- /dev/null +++ b/src/main/views/features/generalApplication/additionalDocuments/check-answers.njk @@ -0,0 +1,51 @@ +{% extends "claim-details-tpl-dashboard.njk" %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "../../../macro/ga-statement-of-truth.njk" import statementOfTruth %} +{% from "../../../macro/heading-with-caption.njk" import headingWithCaption %} +{% from "../../../macro/page-title.njk" import setPageTitle %} +{% from "../../../macro/csrf.njk" import csrfProtection %} + +{% block pageTitle %} + {{ setPageTitle(t, 'PAGES.CHECK_YOUR_ANSWER.TITLE') }} +{% endblock %} + +{% set submitText = t('COMMON.BUTTONS.SUBMIT_CLAIM') %} +{% set labelHtml = t('PAGES.GENERAL_APPLICATION.CHECK_YOUR_ANSWER.STATEMENT_OF_TRUTH.CHECKBOX') + + '

' + + t('PAGES.CHECK_YOUR_ANSWER.CLAIM_CHECKBOX_PROCEEDINGS') + +'

' %} +{% set claimantName = claim.getClaimantFullName() %} +{% set defendantName = claim.getDefendantFullName() %} + +{% block nestedContent %} + {{ headingWithCaption(t,'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.ADDITIONAL_DOCUMENTS_CAPTION', 'PAGES.CHECK_YOUR_ANSWER.TITLE') }} + {% if claimIdPrettified %} +

+ {{ t('COMMON.CASE_NUMBER') }} + {{ claimIdPrettified }} +

+ {% endif %} + {% if claimantName and defendantName %} +

{{ claimantName }} v {{ defendantName }}

+ {% endif %} + {% if summaryRows %} +
+
+ {{ govukSummaryList({ + rows: summaryRows + }) }} +
+
+ {% endif %} +
+ {{ csrfProtection(csrf) }} +
+ {{ govukButton({ + text: t('COMMON.BUTTONS.SUBMIT') + }) }} + {{ t('COMMON.BUTTONS.CANCEL') }} +
+
+{% endblock %} diff --git a/src/main/views/features/generalApplication/additionalDocuments/submitted.njk b/src/main/views/features/generalApplication/additionalDocuments/submitted.njk new file mode 100644 index 00000000000..2ecdac16287 --- /dev/null +++ b/src/main/views/features/generalApplication/additionalDocuments/submitted.njk @@ -0,0 +1,26 @@ +{% extends "claim-details-tpl.njk" %} +{% from "../../../macro/csrf.njk" import csrfProtection %} +{% from "../../dashboard/item-content.njk" import itemContent %} + +{% block content %} +
+
+
+
+ {{ csrfProtection(csrf) }} + {% for content in gaPaymentSuccessfulPanel %} + {{ itemContent(content,t) }} + {% endfor %} + {% for content in gaPaymentSuccessfulBody %} + {{ itemContent(content,t) }} + {% endfor %} +
+ {% for content in gaPaymentSuccessfulButton %} + {{ itemContent(content,t) }} + {% endfor %} +
+
+
+
+
+{% endblock %} diff --git a/src/main/views/features/generalApplication/additionalDocuments/upload-additional-documents.njk b/src/main/views/features/generalApplication/additionalDocuments/upload-additional-documents.njk new file mode 100644 index 00000000000..cf1716eb99d --- /dev/null +++ b/src/main/views/features/generalApplication/additionalDocuments/upload-additional-documents.njk @@ -0,0 +1,94 @@ +{% extends "claim-details-tpl-dashboard.njk" %} +{% from "govuk/components/button/macro.njk" import govukButton %} +{% from "govuk/components/summary-list/macro.njk" import govukSummaryList %} +{% from "govuk/components/file-upload/macro.njk" import govukFileUpload %} +{% from "govuk/components/input/macro.njk" import govukInput %} +{% from "../../../macro/page-title.njk" import setPageTitle %} +{% from "../../../macro/csrf.njk" import csrfProtection %} +{% from "../../../macro/heading-with-caption.njk" import headingWithCaption %} +{% from "../../../macro/button.njk" import addButton %} +{% block pageTitle %} + {{ setPageTitle(t, 'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.UPLOAD_DOCUMENTS_TITLE') }} +{% endblock %} +{% block nestedContent %} + {% set errors = "" %} + {% set idValue = "selectedFile" %} + {% if form.hasErrors() %} + {% set errors = translateErrors(form.getAllErrors(),t)[0] %} + {% set idValue = errors.fieldName %} + {% endif %} + {% if errors.text and errors.fieldName and errors.fieldName !== 'typeOfDocument' %} + {% set uploadErrorMsg = errors.text %} + {% endif %} + {{ headingWithCaption(t, 'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.ADDITIONAL_DOCUMENTS_CAPTION', 'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.UPLOAD_DOCUMENTS_TITLE') }} +

{{ t('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.ADDITIONAL_DOCUMENTS_ROW1') }}

+

{{ t('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.ADDITIONAL_DOCUMENTS_ROW2') }}

+

{{ t('PAGES.GENERAL_APPLICATION.UPLOAD_DOCUMENTS.DOCUMENT_TYPE_SIZE') }}

+
+ {{ csrfProtection(csrf) }} +
+ {{ govukInput({ + label: { + text: t('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.TYPE_OF_DOCUMENT') + }, + hint: { + text: t('PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.TYPE_OF_DOCUMENT_HINT') + }, + id: "typeOfDocument", + name: "typeOfDocument", + errorMessage: { + text: t(form.errorFor('typeOfDocument')) + } if t(form.errorFor('typeOfDocument')) else '' + }) }} + + {{ govukFileUpload({ + id: idValue, + name: "selectedFile", + label: { + html: 'Upload a file' + }, + errorMessage: { text: t(uploadErrorMsg) } if uploadErrorMsg else '' + }) }} + {{ govukButton({ + id: 'uploadFileButton', + name: 'action', + value: 'uploadButton', + text: t('COMMON.UPLOAD_FILE'), + classes: "govuk-button--secondary govuk-!-margin-bottom-0" + }) }} +
+ {% if formattedSummary.summaryList.rows.length > 0 %} +

{{ t('PAGES.GENERAL_APPLICATION.UPLOAD_DOCUMENTS.LIST_TITLE') }}

+
+ {% for row in formattedSummary.summaryList.rows %} +
+ {% if row.value.html %} +
+
+ {{ row.key.text }} +
+
+

{{row.value.html}}

+
+
+ {% else %} + + {% endif %} +
+ {% endfor %} + {% endif %} +
+ {{ addButton(t, 'CONTINUE', cancelUrl) }} +
+
+{% endblock %} \ No newline at end of file diff --git a/src/main/views/features/generalApplication/view-applications.njk b/src/main/views/features/generalApplication/view-applications.njk index e0d4158cfd4..708f5345995 100644 --- a/src/main/views/features/generalApplication/view-applications.njk +++ b/src/main/views/features/generalApplication/view-applications.njk @@ -34,7 +34,7 @@

{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.SUBMITTED') }}

{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.EITHER_PARTIES') }}

{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.ADDITIONAL_SECTIONS') }}

-

{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_1')}}{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_2') }}{{t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_3') }}

+

{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_1')}}{{ t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_2') }}{{t('PAGES.GENERAL_APPLICATION.VIEW_APPLICATION.UPLOAD_DOCUMENTS_3') }}

{{ govukDetails({ diff --git a/src/test/a11y/a11y.mock-test.ts b/src/test/a11y/a11y.mock-test.ts index 39fe11cd292..30e95b3bf28 100644 --- a/src/test/a11y/a11y.mock-test.ts +++ b/src/test/a11y/a11y.mock-test.ts @@ -63,6 +63,7 @@ describe('Accessibility', async () => { it('Test of '+url,async () => { app.get(url, (req: any, res: any) => { url = url.replace(':id', '1645882162449409'); + url = url.replace(':appId', '1720536653906339'); const filePath = translateUrlToFilePath(url); const fileContent = fs.readFileSync(filePath, 'utf8'); res.send(fileContent); diff --git a/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/checkAnswersController.test.ts b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/checkAnswersController.test.ts new file mode 100644 index 00000000000..9683efe2fae --- /dev/null +++ b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/checkAnswersController.test.ts @@ -0,0 +1,98 @@ +import config from 'config'; +import nock from 'nock'; +import request from 'supertest'; +import { app } from '../../../../../../../main/app'; +import { isGaForLipsEnabled } from 'app/auth/launchdarkly/launchDarklyClient'; +import { buildSummarySectionForAdditionalDoc, getClaimDetailsById, prepareCCDData } from 'services/features/generalApplication/additionalDocumentService'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL } from 'routes/urls'; +import { Claim } from 'common/models/claim'; +import { GeneralApplication } from 'common/models/generalApplication/GeneralApplication'; +import { GaServiceClient } from 'client/gaServiceClient'; +import { ApplicationEvent } from 'common/models/gaEvents/applicationEvent'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; + +jest.mock('../../../../../../../main/modules/oidc'); +jest.mock('../../../../../../../main/app/auth/launchdarkly/launchDarklyClient'); +jest.mock('../../../../../../../main/services/features/generalApplication/additionalDocumentService', () => ({ + getClaimDetailsById: jest.fn(), + prepareCCDData: jest.fn(), + buildSummarySectionForAdditionalDoc: jest.fn(), +})); +jest.mock('../../../../../../../main/services/features/generalApplication/generalApplicationService', () => ({ + getCancelUrl: jest.fn(), +})); + +describe('General Application - additional docs check answer controller ', () => { + const citizenRoleToken: string = config.get('citizenRoleToken'); + const idamUrl: string = config.get('idamUrl'); + beforeAll(() => { + nock(idamUrl) + .post('/o/token') + .reply(200, { id_token: citizenRoleToken }); + (isGaForLipsEnabled as jest.Mock).mockResolvedValue(true); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('GET check your answers', () => { + it('should render the check answers page with correct data', async () => { + const claimId = '123'; + const gaId = '456'; + const claim = new Claim(); + claim.generalApplication = new GeneralApplication(); + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (getCancelUrl as jest.Mock).mockResolvedValue('/cancel-url'); + (buildSummarySectionForAdditionalDoc as jest.Mock).mockReturnValue([]); + + const res = await request(app).get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)); + + expect(res.status).toBe(200); + expect(getClaimDetailsById).toHaveBeenCalledWith(expect.anything()); + expect(getCancelUrl).toHaveBeenCalledWith(claimId, claim); + expect(buildSummarySectionForAdditionalDoc).toHaveBeenCalledWith(claim.generalApplication.uploadAdditionalDocuments, claimId, gaId); + expect(res.text).toContain('Check your answers'); + }); + + it('should handle errors', async () => { + const claimId = '123'; + const gaId = '456'; + (getClaimDetailsById as jest.Mock).mockRejectedValue(new Error('Error')); + + const res = await request(app).get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)); + + expect(res.status).toBe(500); + }); + }); + + describe('POST /ga-upload-additional-documents-cya', () => { + it('should submit the documents and redirect to submitted page', async () => { + const claimId = '123'; + const gaId = '456'; + const claim = new Claim(); + claim.generalApplication = new GeneralApplication(); + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (prepareCCDData as jest.Mock).mockReturnValue([]); + const mockGaServiceClient = jest.spyOn(GaServiceClient.prototype, 'submitEvent').mockResolvedValueOnce(undefined); + + const res = await request(app).post(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)); + + expect(res.status).toBe(302); + expect(res.header.location).toBe(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL)); + expect(getClaimDetailsById).toHaveBeenCalledWith(expect.anything()); + expect(prepareCCDData).toHaveBeenCalledWith(claim.generalApplication.uploadAdditionalDocuments); + expect(mockGaServiceClient).toHaveBeenCalledWith(ApplicationEvent.UPLOAD_ADDL_DOCUMENTS, gaId, { uploadDocument: [] }, expect.anything()); + }); + + it('should handle errors', async () => { + const claimId = '123'; + const gaId = '456'; + (getClaimDetailsById as jest.Mock).mockRejectedValue(new Error('Error')); + + const res = await request(app).post(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)).send({}); + + expect(res.status).toBe(500); + }); + }); +}); \ No newline at end of file diff --git a/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/submittedController.test.ts b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/submittedController.test.ts new file mode 100644 index 00000000000..b682e104f50 --- /dev/null +++ b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/submittedController.test.ts @@ -0,0 +1,65 @@ +import config from 'config'; +import nock from 'nock'; +import request from 'supertest'; +import { app } from '../../../../../../../main/app'; +import { isGaForLipsEnabled } from 'app/auth/launchdarkly/launchDarklyClient'; +import { getClaimById } from 'modules/utilityService'; +import { deleteDraftClaimFromStore, generateRedisKey } from 'modules/draft-store/draftStoreService'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL } from 'routes/urls'; +import { Claim } from 'common/models/claim'; + +jest.mock('../../../../../../../main/modules/oidc'); +jest.mock('../../../..../../../../../../main/modules/utilityService'); +jest.mock('../../../../../../../main/app/auth/launchdarkly/launchDarklyClient'); +jest.mock('../../../../../../../main/modules/draft-store/draftStoreService', () => ({ + generateRedisKey: jest.fn((key) => key), + deleteDraftClaimFromStore: jest.fn((key) => { key; }), +})); +jest.mock('../../../../../../../main/services/features/generalApplication/generalApplicationService', () => ({ + getCancelUrl: jest.fn(), +})); + +describe('General Application - additional docs submitted controller', () => { + const citizenRoleToken: string = config.get('citizenRoleToken'); + const idamUrl: string = config.get('idamUrl'); + beforeAll(() => { + nock(idamUrl) + .post('/o/token') + .reply(200, { id_token: citizenRoleToken }); + (isGaForLipsEnabled as jest.Mock).mockResolvedValue(true); + }); + beforeEach(() => { + jest.clearAllMocks(); + }); + describe('GET submit controller', () => { + it('should render the submitted page with correct data', async () => { + const claimId = '123'; + const claim = new Claim(); + const redisKey = 'redis-key'; + const cancelUrl = '/cancel-url'; + + (getClaimById as jest.Mock).mockResolvedValue(claim); + (generateRedisKey as jest.Mock).mockReturnValue(redisKey); + (deleteDraftClaimFromStore as jest.Mock).mockResolvedValue(undefined); + (getCancelUrl as jest.Mock).mockResolvedValue(cancelUrl); + + const res = await request(app).get(GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL.replace(':id', claimId)); + + expect(res.status).toBe(200); + expect(getClaimById).toHaveBeenCalledWith(claimId, expect.anything(), true); + expect(generateRedisKey).toHaveBeenCalledWith(expect.anything()); + expect(deleteDraftClaimFromStore).toHaveBeenCalledWith(redisKey); + expect(getCancelUrl).toHaveBeenCalledWith(claimId, claim); + expect(res.text).toContain('You\'ve uploaded additional documents'); + }); + + it('should handle errors', async () => { + (getClaimById as jest.Mock).mockRejectedValue(new Error('Error')); + + const res = await request(app).get(GA_UPLOAD_ADDITIONAL_DOCUMENTS_SUBMITTED_URL.replace(':id', '123')); + + expect(res.status).toBe(500); + }); + }); +}); \ No newline at end of file diff --git a/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.test.ts b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.test.ts new file mode 100644 index 00000000000..4e1dedcd8ab --- /dev/null +++ b/src/test/unit/routes/features/claim/generalApplication/additionalDocuments/uploadAdditionalDocumentsController.test.ts @@ -0,0 +1,235 @@ +import config from 'config'; +import nock from 'nock'; +import request from 'supertest'; +import { app } from '../../../../../../../main/app'; +import { + getClaimDetailsById, + getSummaryList, + removeSelectedDocument, + uploadSelectedFile, +} from 'services/features/generalApplication/additionalDocumentService'; +import { generateRedisKey } from 'modules/draft-store/draftStoreService'; +import { getCancelUrl } from 'services/features/generalApplication/generalApplicationService'; +import { + GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL, + GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL, +} from 'routes/urls'; +import { Claim } from 'common/models/claim'; +import { GeneralApplication } from 'common/models/generalApplication/GeneralApplication'; +import { isGaForLipsEnabled } from 'app/auth/launchdarkly/launchDarklyClient'; +import { UploadAdditionalDocument } from 'common/models/generalApplication/UploadAdditionalDocument'; +import { CaseDocument } from 'common/models/document/caseDocument'; +import { FileUpload } from 'common/models/caseProgression/uploadDocumentsUserForm'; +import { Session } from 'express-session'; +import { t } from 'i18next'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; + +jest.mock('../../../../../../../main/modules/oidc'); +jest.mock('../../../../../../../main/services/features/generalApplication/additionalDocumentService', () => ({ + getClaimDetailsById: jest.fn(), + getSummaryList: jest.fn(), + uploadSelectedFile: jest.fn(), + removeSelectedDocument: jest.fn(), +})); +jest.mock('../../../../../../../main/app/auth/launchdarkly/launchDarklyClient'); +jest.mock('../../../../../../../main/modules/draft-store/draftStoreService', () => ({ + generateRedisKey: jest.fn((key) => key), +})); + +jest.mock('../../../../../../../main/services/features/generalApplication/generalApplicationService', () => ({ + getCancelUrl: jest.fn(), +})); + +describe('uploadAdditionalDocumentsController', () => { + const citizenRoleToken: string = config.get('citizenRoleToken'); + const idamUrl: string = config.get('idamUrl'); + const claimId = '123'; + const gaId = '456'; + let claim: Claim; + beforeAll(() => { + nock(idamUrl) + .post('/o/token') + .reply(200, { id_token: citizenRoleToken }); + (isGaForLipsEnabled as jest.Mock).mockResolvedValue(true); + }); + + beforeEach(() => { + claim = new Claim(); + claim.generalApplication = new GeneralApplication(); + jest.clearAllMocks(); + }); + + describe('on Get request', () => { + it('should render the upload additional documents page with correct data', async () => { + const redisKey = 'redis-key'; + const cancelUrl = '/cancel-url'; + const formattedSummaryList = { + title: '', + summaryList: { + rows: [ + { + key: { + text: 'Type of document', + }, + value: { + html: 'test', + }, + }, + { + key: { + text: 'n245form.pdf', + }, + value: { + html: '', + }, + actions: { + items: [ + { + href: '/case/1720536503257495/general-application/1720536653906339/upload-additional-documents?indexId=1', + text: 'Remove document', + visuallyHiddenText: 'n245form.pdf', + }, + ], + }, + }, + ], + }, + }; + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (generateRedisKey as jest.Mock).mockReturnValue(redisKey); + (getCancelUrl as jest.Mock).mockResolvedValue(cancelUrl); + (getSummaryList as jest.Mock).mockReturnValue(formattedSummaryList); + + const res = await request(app).get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)); + + expect(res.status).toBe(200); + expect(getClaimDetailsById).toHaveBeenCalledWith(expect.anything()); + expect(generateRedisKey).toHaveBeenCalledWith(expect.anything()); + expect(getCancelUrl).toHaveBeenCalledWith(claimId, claim); + expect(getSummaryList).toHaveBeenCalledWith(claim.generalApplication.uploadAdditionalDocuments, claimId, gaId); + expect(res.text).toContain('Type of document'); + expect(res.text).toContain('test'); + }); + + it('should remove the selected doc from the store', async () => { + + const additionalDocument = new UploadAdditionalDocument(); + additionalDocument.typeOfDocument = 'testt'; + additionalDocument.caseDocument = { + documentLink: { document_binary_url: 'binary_url1', document_filename: 'testDoc', document_url: 'url' }, + documentName: 'testDoc', + documentType: null, + documentSize: 1000, + createdDatetime: new Date(), + } as CaseDocument; + additionalDocument.fileUpload = new FileUpload(); + claim.generalApplication.uploadAdditionalDocuments = [additionalDocument]; + const redisKey = 'redis-key'; + const cancelUrl = '/cancel-url'; + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (generateRedisKey as jest.Mock).mockReturnValue(redisKey); + (getCancelUrl as jest.Mock).mockResolvedValue(cancelUrl); + (getSummaryList as jest.Mock).mockReturnValue({}); + (removeSelectedDocument as jest.Mock).mockReturnValueOnce(void 0); + + const res = await request(app).get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)).query({ indexId: 1 }); + + expect(res.status).toBe(200); + expect(removeSelectedDocument).toHaveBeenCalledWith(redisKey, claim, 0); + }); + + it('should return page with errors when upload file button clicked without choosing file', async () => { + const errors = [ + { + target: {}, + property: 'fileUpload', + constraints: { isNotEmpty: 'ERRORS.VALID_CHOOSE_THE_FILE' }, + fieldName: 'fileUpload', + text: 'Choose the file you want to upload', + href: '#fileUpload', + }, + ]; + const additionalDocument = new UploadAdditionalDocument(); + additionalDocument.typeOfDocument = 'testt'; + additionalDocument.caseDocument = { + documentLink: { document_binary_url: 'binary_url1', document_filename: 'testDoc', document_url: 'url' }, + documentName: 'testDoc', + documentType: null, + documentSize: 1000, + createdDatetime: new Date(), + } as CaseDocument; + additionalDocument.fileUpload = new FileUpload(); + claim.generalApplication.uploadAdditionalDocuments = [additionalDocument]; + const redisKey = 'redis-key'; + const cancelUrl = '/cancel-url'; + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (generateRedisKey as jest.Mock).mockReturnValue(redisKey); + (getCancelUrl as jest.Mock).mockResolvedValue(cancelUrl); + (getSummaryList as jest.Mock).mockReturnValue({}); + (removeSelectedDocument as jest.Mock).mockReturnValueOnce(void 0); + app.request.session = { fileUpload: JSON.stringify(errors) } as unknown as Session; + await request(app) + .get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)) + .expect((res) => { + expect(res.status).toBe(200); + expect(res.text).toContain(t('ERRORS.VALID_CHOOSE_THE_FILE')); + }); + }); + + it('should handle errors', async () => { + (getClaimDetailsById as jest.Mock).mockRejectedValue(new Error('Error')); + + const res = await request(app).get(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)); + + expect(res.status).toBe(500); + }); + }); + + describe('on post request', () => { + it('should handle file upload and redirect to the same page', async () => { + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + (uploadSelectedFile as jest.Mock).mockResolvedValueOnce(void 0); + + const res = await request(app) + .post(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)) + .field('action', 'uploadButton') + .attach('selectedFile', Buffer.from('file content'), 'test-file.txt'); + + expect(res.status).toBe(302); + expect(res.header['location']).toBe(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)); + expect(uploadSelectedFile).toHaveBeenCalledWith(expect.anything(), claim); + }); + + it('should redirect to CYA page if documents are uploaded', async () => { + const additionalDocument = new UploadAdditionalDocument(); + additionalDocument.typeOfDocument = 'testt'; + additionalDocument.caseDocument = { + documentLink: { document_binary_url: 'binary_url1', document_filename: 'testDoc', document_url: 'url' }, + documentName: 'testDoc', + documentType: null, + documentSize: 1000, + createdDatetime: new Date(), + } as CaseDocument; + additionalDocument.fileUpload = new FileUpload(); + claim.generalApplication.uploadAdditionalDocuments = [additionalDocument]; + + (getClaimDetailsById as jest.Mock).mockResolvedValue(claim); + + const res = await request(app) + .post(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)); + + expect(res.status).toBe(302); + expect(res.header['location']).toBe(constructResponseUrlWithIdAndAppIdParams(claimId, gaId, GA_UPLOAD_ADDITIONAL_DOCUMENTS_CYA_URL)); + }); + + it('should handle errors', async () => { + (getClaimDetailsById as jest.Mock).mockRejectedValue(new Error('Error')); + + const res = await request(app) + .post(constructResponseUrlWithIdAndAppIdParams('123', '456', GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)) + .field('action', 'submit'); + + expect(res.status).toBe(500); + }); + }); +}); diff --git a/src/test/unit/services/features/generalApplication/additionalDocumentService.test.ts b/src/test/unit/services/features/generalApplication/additionalDocumentService.test.ts new file mode 100644 index 00000000000..b2b815680e2 --- /dev/null +++ b/src/test/unit/services/features/generalApplication/additionalDocumentService.test.ts @@ -0,0 +1,244 @@ +import { CivilServiceClient } from 'client/civilServiceClient'; +import { AppRequest, AppSession } from 'common/models/AppRequest'; +import { FileUpload } from 'common/models/caseProgression/fileUpload'; +import { Claim } from 'common/models/claim'; +import { CaseDocument } from 'common/models/document/caseDocument'; +import { GeneralApplication } from 'common/models/generalApplication/GeneralApplication'; +import { UploadAdditionalDocument } from 'common/models/generalApplication/UploadAdditionalDocument'; +import { summaryRow } from 'common/models/summaryList/summaryList'; +import { constructResponseUrlWithIdAndAppIdParams } from 'common/utils/urlFormatter'; +import { generateRedisKey, saveDraftClaim } from 'modules/draft-store/draftStoreService'; +import { getClaimById } from 'modules/utilityService'; +import { GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL } from 'routes/urls'; +import { TypeOfDocumentSectionMapper } from 'services/features/caseProgression/TypeOfDocumentSectionMapper'; +import { getClaimDetailsById, getContentForBody, getContentForCloseButton, getContentForPanel, getSummaryList, prepareCCDData, removeSelectedDocument, uploadSelectedFile } from 'services/features/generalApplication/additionalDocumentService'; + +const { Logger } = require('@hmcts/nodejs-logging'); +const logger = Logger.getLogger('additionalDocumentService'); + +jest.mock('uuid', () => ({ + v4: jest.fn().mockReturnValue('mocked-uuid'), +})); +jest.mock('../../../../../main/modules/draft-store/draftStoreService', () => ({ + saveDraftClaim: jest.fn(), + generateRedisKey: jest.fn(), +})); +jest.mock('../../../../../main/modules/utilityService'); +jest.mock('i18next', () => ({ + t: jest.fn((key) => key), +})); +jest.mock('../../../../../main/services/features/caseProgression/TypeOfDocumentSectionMapper', () => ({ + TypeOfDocumentSectionMapper: { + mapToSingleFile: jest.fn(), + }, +})); + +describe('Additional Documents Service', () => { + let claim: Claim; + beforeEach(() => { + claim = new Claim(); + claim.generalApplication = new GeneralApplication(); + jest.clearAllMocks(); + }); + describe('uploadSelectedFile', () => { + it('should upload file and save draft claim if no errors', async () => { + const req = { + body: { typeOfDocument: 'Type1' }, + session: {} as AppSession, + } as unknown as AppRequest; + const fileUpload = { name: 'file' }; + const uploadedDocument = { + documentName: 'Document1', + createdBy: 'User1', + documentLink: { document_url: 'url', document_filename: 'filename', document_binary_url: 'binaryUrl' }, + documentType: null, + documentSize: 123, + } as CaseDocument; + (TypeOfDocumentSectionMapper.mapToSingleFile as jest.Mock).mockReturnValue(fileUpload); + const mockValue = jest.spyOn(CivilServiceClient.prototype, 'uploadDocument').mockResolvedValue(uploadedDocument as unknown as CaseDocument); + + await uploadSelectedFile(req, claim as Claim); + + expect(TypeOfDocumentSectionMapper.mapToSingleFile).toHaveBeenCalledWith(req); + expect(mockValue).toHaveBeenCalledWith(req, fileUpload); + expect(claim.generalApplication.uploadAdditionalDocuments).toHaveLength(1); + expect(saveDraftClaim).toHaveBeenCalledWith(generateRedisKey(req), claim); + }); + + it('should set errors in session if validation fails', async () => { + const req = { + body: { typeOfDocument: 'Type1' }, + session: { fileUpload: undefined } as AppSession, + } as unknown as AppRequest; + + (TypeOfDocumentSectionMapper.mapToSingleFile as jest.Mock).mockReturnValue(undefined); + + await uploadSelectedFile(req, claim); + + expect(req.session.fileUpload).toBeDefined(); + }); + }); + describe('getSummaryList', () => { + it('should generate a summary list', () => { + const additionalDocumentsList: UploadAdditionalDocument[] = [ + { + typeOfDocument: 'Type1', + caseDocument: { + documentName: 'Document1', + createdBy: 'User1', + documentLink: { document_url: 'ur1', document_filename: 'filename1', document_binary_url: 'binaryUrl1' }, + documentType: null, + documentSize: 123, + } as CaseDocument, + fileUpload: {} as FileUpload, + }, + { + typeOfDocument: 'Type2', + caseDocument: { + documentName: 'Document2', + createdBy: 'User2', + documentLink: { document_url: 'ur2', document_filename: 'filename2', document_binary_url: 'binaryUrl2' }, + documentType: null, + documentSize: 123, + } as CaseDocument, + fileUpload: {} as FileUpload, + }, + ]; + const claimId = '1'; + const gaId = '2'; + + const result = getSummaryList(additionalDocumentsList, claimId, gaId); + + expect(result.summaryList.rows).toHaveLength(4); + expect(result.summaryList.rows[0]).toEqual(summaryRow('Type of document', 'Type1')); + expect(result.summaryList.rows[1]).toEqual(summaryRow('Document1', '', `${constructResponseUrlWithIdAndAppIdParams(claimId, gaId,GA_UPLOAD_ADDITIONAL_DOCUMENTS_URL)}?indexId=1`, 'Remove document')); + }); + }); + + describe('getClaimDetailsById', () => { + it('should retrieve and return claim details', async () => { + const req: AppRequest = { + params: { id: '1' }, + } as unknown as AppRequest; + claim.generalApplication = undefined; + (getClaimById as jest.Mock).mockResolvedValue(claim); + + const result = await getClaimDetailsById(req); + + expect(result.generalApplication.uploadAdditionalDocuments).toEqual([]); + expect(getClaimById).toHaveBeenCalledWith(req.params.id, req, true); + }); + }); + describe('prepareCCDData', () => { + it('should correctly map UploadAdditionalDocuments to CCD format', () => { + const uploadAdditionalDocuments: UploadAdditionalDocument[] = [ + { + typeOfDocument: 'Type1', + caseDocument: { + documentName: 'Document1', + createdBy: 'User1', + documentLink: { + document_url: 'url1', + document_binary_url: 'binaryUrl1', + document_filename: 'filename1', + }, + documentType: null, + documentSize: 123, + } as CaseDocument, + fileUpload: {} as FileUpload, + }, + { + typeOfDocument: 'Type2', + caseDocument: { + documentName: 'Document2', + createdBy: 'User2', + documentLink: { + document_url: 'url2', + document_binary_url: 'binaryUrl2', + document_filename: 'filename2', + }, + documentType: null, + documentSize: 456, + } as CaseDocument, + fileUpload: {} as FileUpload, + }, + ]; + + const result = prepareCCDData(uploadAdditionalDocuments); + + expect(result).toHaveLength(2); + result.forEach((item, index) => { + expect(item.id).toBe('mocked-uuid'); + expect(item.value.typeOfDocument).toBe(uploadAdditionalDocuments[index].typeOfDocument); + expect(item.value.documentUpload.document_url).toBe(uploadAdditionalDocuments[index].caseDocument.documentLink.document_url); + expect(item.value.documentUpload.document_binary_url).toBe(uploadAdditionalDocuments[index].caseDocument.documentLink.document_binary_url); + expect(item.value.documentUpload.document_filename).toBe(uploadAdditionalDocuments[index].caseDocument.documentName); + }); + }); + }); + describe('removeSelectedDocument', () => { + it('should remove selected document and save draft claim', async () => { + const redisKey = 'key'; + const claim: Claim = { + generalApplication: { + uploadAdditionalDocuments: [ + { + typeOfDocument: 'Type1', caseDocument: { + documentName: 'Document2', + createdBy: 'User2', + documentLink: { document_url: 'ur2', document_filename: 'filename2', document_binary_url: 'binaryUrl2' }, + documentType: null, + documentSize: 123, + } as CaseDocument, + fileUpload: {} as FileUpload, + }, + ], + } as GeneralApplication, + } as Claim; + const index = 0; + + await removeSelectedDocument(redisKey, claim, index); + + expect(saveDraftClaim).toHaveBeenCalledWith(redisKey, claim); + expect(claim.generalApplication.uploadAdditionalDocuments).toHaveLength(0); + }); + + it('should handle error during document removal', async () => { + const redisKey = 'key'; + claim.generalApplication.uploadAdditionalDocuments.push({ typeOfDocument: 'Type1', caseDocument: { documentName: 'Document1' } as CaseDocument, fileUpload: {} as FileUpload }); + const index = 0; + const error = new Error('Error'); + (saveDraftClaim as jest.Mock).mockRejectedValue(error); + + const loggerErrorSpy = jest.spyOn(logger, 'error'); + + await expect(removeSelectedDocument(redisKey, claim, index)).rejects.toThrow(error); + expect(loggerErrorSpy).toHaveBeenCalledWith(error); + }); + }); + + describe('getContentForPanel', () => { + it('should return built content for panel', () => { + const lng = 'en'; + const result = getContentForPanel(lng); + expect(result).toEqual([{ 'data': { 'title': 'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.UPLOADED_ADDITIONAL_DOCS' }, 'type': 'panel' }]); + }); + }); + + describe('getContentForBody', () => { + it('should return built content for body', () => { + const lng = 'en'; + const result = getContentForBody(lng); + + expect(result).toEqual([{ 'data': { 'classes': undefined, 'text': 'PAGES.GENERAL_APPLICATION.GA_PAYMENT_SUCCESSFUL.WHAT_HAPPENS_NEXT', 'variables': { 'lng': 'en' } }, 'type': 'title' }, { 'data': { 'classes': undefined, 'text': 'PAGES.GENERAL_APPLICATION.ADDITIONAL_DOCUMENTS.JUDGE_WILL_REVIEW', 'variables': { 'lng': 'en' } }, 'type': 'p' }]); + }); + }); + + describe('getContentForCloseButton', () => { + it('should return built content for close button', () => { + const redirectUrl = '/redirect-url'; + const result = getContentForCloseButton(redirectUrl); + expect(result).toEqual([{ 'data': { 'href': '/redirect-url', 'text': 'COMMON.BUTTONS.CLOSE_AND_RETURN_TO_DASHBOARD' }, 'type': 'button' }]); + }); + }); +}); diff --git a/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-check-and-send.html b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-check-and-send.html new file mode 100644 index 00000000000..86cf64c617a --- /dev/null +++ b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-check-and-send.html @@ -0,0 +1,542 @@ + + + + + + + + + + Check your answers - Your money claims account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + + + + + + +
+
+ + +
+ + + + Money Claims + + + + + + +
+ +
+
+ + + + + + +
+ + + +
+

+ + + This is a new service – your feedback will help us to improve it.English + +

+
+ + + + + + Back + + + + + +
+ +
+ +
+
+
+ + +

+ Upload additional documents + Check your answers +

+ + +

+ Case number: + 1720 5365 0325 7495 +

+ + +

Mr Claimant person v mr defendant person

+ + +
+
+ + + + + +
+ + +
+
+ Type of document +
+
+ test +
+ +
+ + + +
+
+ Uploaded document +
+
+ n245form.pdf +
+ +
+ Change Uploaded document + +
+ +
+ + +
+ +
+
+ +
+ + + +
+ + + + + + + + Cancel +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + Contact us for help + + +
+ + + + + + +

Email

+ + + + contactocmc@justice.gov.uk +

+ + + + + + +

Telephone

+ + 0300 123 7050 + + +

Monday to Friday, 8.30am to 5pm.

+ + + Find out about call charges (opens in a new window)

+ + +
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + + + + + diff --git a/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-submitted.html b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-submitted.html new file mode 100644 index 00000000000..6aa9257125c --- /dev/null +++ b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents-submitted.html @@ -0,0 +1,645 @@ + + + + + + + + + + Your money claims account - Money Claims + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + + + + + + +
+
+ + +
+ + + + Money Claims + + + + + + +
+ +
+
+ + + + + + +
+ + +
+

+ + + This is a new service – your feedback will help us to improve it.English + +

+
+ + + + + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+

+ You've uploaded additional documents +

+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

What happens next

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

A judge will review the additional documents that you’ve uploaded. You’ll receive an update with their decision or next steps.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+ + + + + + + + + + + + + + + diff --git a/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents.html b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents.html new file mode 100644 index 00000000000..8ea45009400 --- /dev/null +++ b/src/test/utils/mocks/a11y/mock-case-1645882162449409-general-application-1720536653906339-upload-additional-documents.html @@ -0,0 +1,592 @@ + + + + + + + + + + Upload documents - Your money claims account + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Skip to main content + + + + + + + +
+
+ + +
+ + + + Money Claims + + + + + + +
+ +
+
+ + + + + + +
+ + + +
+

+ + + This is a new service – your feedback will help us to improve it.English + +

+
+ + + + + + Back + + + + + +
+ +
+ +
+
+
+ + + + + + +

+ Upload additional documents + Upload documents +

+ +

You should only upload documents that are relevant to your application, for example if the judge has ordered a hearing and instructed you to provide documents ahead of this.

+

Before you upload the document you will need to describe the type of document you are uploading, for example, Bundle, witness statement or costs schedule.

+

Each document must be less than 100MB. You can upload the following file types: DOC/DOCX (Word), XLS/XLSM (Excel), PPT/PPTX (PowerPoint), PDF, RTF, TXT, CSV, JPG/JPEG, PNG, BMP, TIF/TIFF.

+
+ + + +
+ + + +
+ + + + +
+ For example, contract, invoice receipt, email, text message, photo, social media message +
+ + + + +
+ + + + + + +
+ + + + + + +
+ + + + + + + + + + +
+ +

Uploaded files

+
+ +
+ +
+
+ Type of document +
+
+

test

+
+
+ +
+ +
+ +
+
+

n245form.pdf

+
+
+ + Remove document + n245form.pdf + +
+
+ +
+ + +
+ + +
+ + + + + + + + + + + + Cancel + + + + +
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+ + + Contact us for help + + +
+ + + + + + +

Email

+ + + + contactocmc@justice.gov.uk +

+ + + + + + +

Telephone

+ + 0300 123 7050 + + +

Monday to Friday, 8.30am to 5pm.

+ + + Find out about call charges (opens in a new window)

+ + +
+
+ + + +
+
+ +
+
+ + + + + + + + + + + + + + + +