Skip to content
Open
1 change: 1 addition & 0 deletions src/main/app/case/definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,7 @@ export const enum State {
OfflineDocumentReceived = 'OfflineDocumentReceived',
PendingHearingOutcome = 'PendingHearingOutcome',
PendingHearingDate = 'PendingHearingDate',
PendingRefund = 'PendingRefund',
BulkCaseReject = 'BulkCaseReject',
RequestedInformationSubmitted = 'RequestedInformationSubmitted',
RespondentFinalOrderRequested = 'RespondentFinalOrderRequested',
Expand Down
28 changes: 28 additions & 0 deletions src/main/app/utils/general-application-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
import { AppRequest } from '../controller/AppRequest';

import {
D11_GENERAL_APPLICATION_EXCLUDED_STATES,
RESPONDENT_ONLY_GENERAL_APPLICATION_EXCLUDED_STATES,
canStartNewGeneralApplication,
findAllOnlineGenAppsForUser,
findGenAppAwaitingDocuments,
Expand Down Expand Up @@ -344,6 +346,32 @@ describe('GeneralApplicationUtils', () => {
expect(canStartNewGeneralApplication(false, mockReq.session.userCase)).toBe(false);
});

D11_GENERAL_APPLICATION_EXCLUDED_STATES.forEach(state => {
test(`should return false for applicant in sole application for excluded state: ${state}`, () => {
mockReq.session.userCase.state = state;
mockReq.session.userCase.applicationType = ApplicationType.SOLE_APPLICATION;
expect(canStartNewGeneralApplication(false, mockReq.session.userCase)).toBe(false);
});
});

[...D11_GENERAL_APPLICATION_EXCLUDED_STATES, ...RESPONDENT_ONLY_GENERAL_APPLICATION_EXCLUDED_STATES].forEach(
state => {
test(`should return false for respondent in sole application for excluded state: ${state}`, () => {
mockReq.session.userCase.state = state;
mockReq.session.userCase.applicationType = ApplicationType.SOLE_APPLICATION;
expect(canStartNewGeneralApplication(true, mockReq.session.userCase)).toBe(false);
});
}
);

D11_GENERAL_APPLICATION_EXCLUDED_STATES.forEach(state => {
test(`should return false for applicant2 in joint application for excluded state: ${state}`, () => {
mockReq.session.userCase.state = state;
mockReq.session.userCase.applicationType = ApplicationType.JOINT_APPLICATION;
expect(canStartNewGeneralApplication(true, mockReq.session.userCase)).toBe(false);
});
});

test('Should return false if general referral in progress', () => {
mockReq.session.userCase.generalReferralType = 'SOME_REFERRAL';

Expand Down
48 changes: 44 additions & 4 deletions src/main/app/utils/general-application-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,36 @@ import {
import { AppRequest } from '../controller/AppRequest';
import { AnyObject } from '../controller/PostController';

const D11_GENERAL_APPLICATION_EXCLUDED_STATES: Set<State> = new Set([
export const D11_GENERAL_APPLICATION_EXCLUDED_STATES: Set<State> = new Set([
State.Applicant2Approved,
State.AwaitingPayment,
State.Rejected,
State.Withdrawn,
State.Archived,
State.AwaitingApplicant2Response,
State.AwaitingGeneralApplicationPayment,
State.AwaitingGenAppDocuments,
State.AwaitingGeneralConsideration,
State.GeneralApplicationReceived,
State.AwaitingGeneralReferralPayment,
State.ConditionalOrderPending,
State.AwaitingJudgeClarification,
State.AwaitingServiceConsideration,
State.AwaitingServicePayment,
State.BailiffRefused,
State.ConditionalOrderDrafted,
State.Draft,
State.GeneralConsiderationComplete,
State.LAServiceReview,
State.PendingRefund,
State.PendingServiceAppResponse,
State.ServiceAdminRefusal,
State.FinalOrderComplete,
]);

export const RESPONDENT_ONLY_GENERAL_APPLICATION_EXCLUDED_STATES: Set<State> = new Set([
State.AwaitingFinalOrderPayment,
State.RespondentFinalOrderRequested,
]);

const APPLICANT1_GEN_APP_PARTY_NAMES = new Set<GeneralParties>([GeneralParties.APPLICANT]);
Expand Down Expand Up @@ -98,8 +122,18 @@ export const hasGenAppSaveAndSignOutContent = (isApplicant2: boolean, userCase:
return isDraftingD11Application || hasD11ApplicationPaymentInProgress;
};

export const canStartNewGeneralApplication = (isApplicant2: boolean, userCase: Partial<CaseWithId>): boolean => {
if (D11_GENERAL_APPLICATION_EXCLUDED_STATES.has(userCase.state as State)) {
export const isGenAppExclusionState = (isApplicant2: boolean, userCase: Partial<CaseWithId>): boolean => {
const state = userCase.state as State;
const isSoleRespondent = isApplicant2 && userCase.applicationType === ApplicationType.SOLE_APPLICATION;

return (
D11_GENERAL_APPLICATION_EXCLUDED_STATES.has(state) ||
(isSoleRespondent && RESPONDENT_ONLY_GENERAL_APPLICATION_EXCLUDED_STATES.has(state))
);
};

export const canSubmitD11GeneralApplication = (isApplicant2: boolean, userCase: Partial<CaseWithId>): boolean => {
if (isGenAppExclusionState(isApplicant2, userCase)) {
return false;
}

Expand All @@ -118,7 +152,13 @@ export const canStartNewGeneralApplication = (isApplicant2: boolean, userCase: P
hasGenAppAwaitingDocuments(false, userCase) || hasGenAppPaymentInProgress(false, userCase);
const app2HasSubmittedGenApp =
hasGenAppAwaitingDocuments(true, userCase) || hasGenAppPaymentInProgress(true, userCase);

return !(app1HasSubmittedGenApp || app2HasSubmittedGenApp);
};

export const canStartNewGeneralApplication = (isApplicant2: boolean, userCase: Partial<CaseWithId>): boolean => {
const submissionAllowed = canSubmitD11GeneralApplication(isApplicant2, userCase);
const genAppHasBeenDrafted = hasGenAppSaveAndSignOutContent(isApplicant2, userCase);

return !(app1HasSubmittedGenApp || app2HasSubmittedGenApp || genAppHasBeenDrafted);
return submissionAllowed && !genAppHasBeenDrafted;
};
4 changes: 4 additions & 0 deletions src/main/modules/state-redirect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
APP_REPRESENTED,
DRAFT_SAVE_AND_SIGN_OUT,
GENERAL_APPLICATION_PAYMENT_CALLBACK,
GEN_APP_WITHDRAW_APPLICATION,
NO_RESPONSE_YET,
PAYMENT_CALLBACK_URL,
PAY_AND_SUBMIT,
Expand Down Expand Up @@ -117,6 +118,9 @@ export class StateRedirectMiddleware {
VIEW_YOUR_ANSWERS,
WITHDRAW_APPLICATION,
WITHDRAW_SERVICE_APPLICATION,
GEN_APP_WITHDRAW_APPLICATION,
RESPONDENT + GEN_APP_WITHDRAW_APPLICATION,
APPLICANT_2 + GEN_APP_WITHDRAW_APPLICATION,
].includes(req.path as PageLink)
) {
return next();
Expand Down
5 changes: 4 additions & 1 deletion src/main/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ApplicationWithdrawnPreIssueGetController } from './steps/applicant1/wi
import * as applicant2AccessCodeContent from './steps/applicant2/enter-your-access-code/content';
import { Applicant2AccessCodeGetController } from './steps/applicant2/enter-your-access-code/get';
import { ApplicationWithdrawnGetController } from './steps/application-withdrawn/get';
import { getRootRedirectPath } from './steps/common/common.content';
import { ContactUsGetController } from './steps/contact-us/get';
import { CookiesGetController } from './steps/cookies/get';
import { DraftApplicationSaveSignOutGetController } from './steps/draft-application-save-sign-out/get';
Expand Down Expand Up @@ -193,7 +194,9 @@ export class Routes {
!getUserSequence(req).some(r => req.path.includes(r.url)) ||
shouldHideRouteFromUser(req)
) {
return shouldRedirectRouteToHub(req) ? res.redirect(HUB_PAGE) : res.redirect('/error');
return shouldRedirectRouteToHub(req)
? res.redirect(getRootRedirectPath(req.session.isApplicant2, req.session.userCase) + HUB_PAGE)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted :)

: res.redirect('/error');
}
next();
}
Expand Down
36 changes: 28 additions & 8 deletions src/main/steps/applicant1/hub-page/hub-links/content.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import { TranslationFn } from '../../../../app/controller/GetController';
import { canStartNewGeneralApplication } from '../../../../app/utils/general-application-utils';
import {
canStartNewGeneralApplication,
canSubmitD11GeneralApplication,
} from '../../../../app/utils/general-application-utils';
import { CommonContent, getRootRedirectPath } from '../../../common/common.content';
import {
APPLICANT_2,
CHECK_CONTACT_DETAILS,
HUB_PAGE_DOWNLOADS,
MAKE_AN_APPLICATION,
MAKE_AN_OFFLINE_APPLICATION,
WITHDRAW_THIS_APPLICATION,
WITHDRAW_THIS_APPLICATION_POST_ISSUE,
} from '../../../urls';
import { areDownloadsAvailable } from '../../downloads/content';

const en = ({ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent, app2OrRespondent: string) => ({
const en = (
{ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent,
app2OrRespondent: string,
canStartOnlineGenApplication: boolean
) => ({
reviewContactDetails: {
url: app2OrRespondent + CHECK_CONTACT_DETAILS,
text: 'Review your contact details',
Expand All @@ -21,7 +29,7 @@ const en = ({ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent, app2O
text: 'View all documents',
},
genAppMakeAnApplication: {
url: app2OrRespondent + MAKE_AN_APPLICATION,
url: app2OrRespondent + (canStartOnlineGenApplication ? MAKE_AN_APPLICATION : MAKE_AN_OFFLINE_APPLICATION),
text: 'Make an application to the court',
},
withdrawApplication: {
Expand All @@ -31,7 +39,11 @@ const en = ({ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent, app2O
});

// @TODO translations
const cy: typeof en = ({ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent, app2OrRespondent) => ({
const cy: typeof en = (
{ isDivorce, isApplicant2, caseHasBeenIssued }: CommonContent,
app2OrRespondent,
canStartOnlineGenApplication: boolean
) => ({
reviewContactDetails: {
url: app2OrRespondent + CHECK_CONTACT_DETAILS,
text: 'Adolygu eich manylion cyswllt',
Expand All @@ -41,7 +53,7 @@ const cy: typeof en = ({ isDivorce, isApplicant2, caseHasBeenIssued }: CommonCon
text: 'Gweld eich dogfennau',
},
genAppMakeAnApplication: {
url: MAKE_AN_APPLICATION,
url: app2OrRespondent + (canStartOnlineGenApplication ? MAKE_AN_APPLICATION : MAKE_AN_OFFLINE_APPLICATION),
text: 'Make an application to the court',
},
withdrawApplication: {
Expand All @@ -56,11 +68,19 @@ const languages = {
};

export const generateContent: TranslationFn = content => {
const showGenApplicationLink = canStartNewGeneralApplication(content.isApplicant2, content.userCase);
const showWithdrawLink = !content.isApplicant2 || (content.isApplicant2 && content.isJointApplication);
const canStartNewOnlineGenApplication = canStartNewGeneralApplication(content.isApplicant2, content.userCase);
const cannotSubmitOnlineD11GenApplication = !canSubmitD11GeneralApplication(content.isApplicant2, content.userCase);
const showGenApplicationLink = canStartNewOnlineGenApplication || cannotSubmitOnlineD11GenApplication;
const showWithdrawLink =
(!content.isApplicant2 || (content.isApplicant2 && content.isJointApplication)) &&
(!content.caseHasBeenIssued || canStartNewOnlineGenApplication);

return {
...languages[content.language](content, getRootRedirectPath(content.isApplicant2, content.userCase)),
...languages[content.language](
content,
getRootRedirectPath(content.isApplicant2, content.userCase),
canStartNewOnlineGenApplication
),
caseHasBeenIssued: content.caseHasBeenIssued,
showDownloadLink: areDownloadsAvailable(content),
showWithdrawLink,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Case, CaseWithId } from '../../../../../app/case/case';
import { InterimApplicationType } from '../../../../../app/case/definition';
import { AppRequest } from '../../../../../app/controller/AppRequest';
import { AnyObject, PostController } from '../../../../../app/controller/PostController';
import { canSubmitD11GeneralApplication } from '../../../../../app/utils/general-application-utils';
import { Step } from '../../../../../steps/applicant1Sequence';
import { getFirstErroredStep } from '../../../../index';

Expand All @@ -26,6 +27,18 @@ export default abstract class CheckAnswersPostController extends PostController<
} else {
formData.applicant1InterimApplicationType = this.interimApplicationType();
}

const interimApplicationType = req.session.isApplicant2
? formData.applicant2InterimApplicationType
: formData.applicant1InterimApplicationType;

if (interimApplicationType === InterimApplicationType.DIGITISED_GENERAL_APPLICATION_D11) {
const canSubmitD11Application = canSubmitD11GeneralApplication(req.session.isApplicant2, req.session.userCase);
if (!canSubmitD11Application) {
throw new Error('Cannot submit a D11 application when there is an existing application in progress');
}
}

return super.save(req, formData, eventName);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,25 @@ describe('CheckGeneralApplicationD11AnswersPostController', () => {
expect(req.locals.api.triggerEvent).toHaveBeenCalledWith('1234', body, CITIZEN_GENERAL_APPLICATION);
});

it('Redirects if a alternative service step is incomplete', async () => {
it('returns an error where a D11 general application cannot be submitted', async () => {
const body = {
applicationType: ApplicationType.SOLE_APPLICATION,
state: State.LAServiceReview,
applicant1InterimAppsStatementOfTruth: Checkbox.Checked,
applicant1InterimApplicationType: InterimApplicationType.DIGITISED_GENERAL_APPLICATION_D11,
};
const req = mockRequest({ body });
req.session.isApplicant2 = false;
const res = mockResponse();

(getFirstErroredStep as jest.Mock).mockReturnValue(undefined);

await controller.post(req, res);

expect(req.session.errors).toBeDefined();
});

it('Redirects if a general application step is incomplete', async () => {
const body = {};
const req = mockRequest({ body });
const res = mockResponse();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import config from 'config';

import { TranslationFn } from '../../../../../app/controller/GetController';
import { CommonContent } from '../../../../common/common.content';

const en = ({ isDivorce, referenceNumber }: CommonContent) => ({
title: 'Making an application to the court',
makeAnApplication: {
header: 'Making an application to the court',
line1: `If you need to make an application to the court as part of your ongoing ${isDivorce ? 'divorce application' : 'application to end your civil partnership'}, you can do so by completing a paper form.`,
line2: 'To make a general application, complete Form D11',
line3: 'You can download the form from GOV.UK:',
d11Text:
'Apply for an interim order as part of divorce, dissolution or separation court proceedings: Form D11 - GOV.UK',
d11Link: config.get('govukUrls.d11Form'),
line4: 'You can send the completed form to the court in the following ways:',
},
documentsByOnlineForm: 'Sending documents using our online form',
documentsByOnlineFormSteps: {
line1: 'You can send photographs or scans of your documents to us by',
line2: 'uploading them using our online form.',
line3:
'Make sure you follow the instructions on how to upload your documents carefully or they could be rejected, resulting in further delays.',
},
documentsByPost: 'Sending your documents by post',
documentsByPostSteps: {
step1: `Write your reference number on each document: ${referenceNumber}`,
step2: 'Post the original documents to:',
},
documentsByPostMoreDetails:
'Make sure you also include in your response a return address. Any cherished documents you send, such as marriage certificates, birth certificates, passports or deed polls will be returned to you. Other documents will not be returned.',
whatHappensNext: {
header: 'What happens next',
line1:
'Once the court receives your general application, we will review it and contact you to let you know the next steps.',
},
returnToHub: 'Return to hub screen',
});

const cy = ({ isDivorce, referenceNumber }: CommonContent) => ({
title: 'Making an application to the court',
makeAnApplication: {
header: 'Making an application to the court',
line1: `If you need to make an application to the court as part of your ongoing ${isDivorce ? 'divorce application' : 'application to end your civil partnership'}, you can do so by completing a paper form.`,
line2: 'To make a general application, complete Form D11',
line3: 'You can download the form from GOV.UK:',
d11Text:
'Apply for an interim order as part of divorce, dissolution or separation court proceedings: Form D11 - GOV.UK',
d11Link: config.get('govukUrls.d11Form'),
line4: 'You can send the completed form to the court in the following ways:',
},
documentsByOnlineForm: 'Sending documents using our online form',
documentsByOnlineFormSteps: {
line1: 'You can send photographs or scans of your documents to us by',
line2: 'uploading them using our online form.',
line3:
'Make sure you follow the instructions on how to upload your documents carefully or they could be rejected, resulting in further delays.',
},
documentsByPost: 'Sending your documents by post',
documentsByPostSteps: {
step1: `Write your reference number on each document: ${referenceNumber}`,
step2: 'Post the original documents to:',
},
documentsByPostMoreDetails:
'Make sure you also include in your response a return address. Any cherished documents you send, such as marriage certificates, birth certificates, passports or deed polls will be returned to you. Other documents will not be returned.',
whatHappensNext: {
header: 'What happens next',
line1:
'Once the court receives your general application, we will review it and contact you to let you know the next steps.',
},
returnToHub: 'Return to hub screen',
});

const languages = {
en,
cy,
};

export const generateContent: TranslationFn = content => {
const translations = languages[content.language](content);
const isRespondent = content.isApplicant2 && !content.isJointApplication;
const isSoleApplicant = !content.isApplicant2 && !content.isJointApplication;
const hasCOBeenGranted = !!content.userCase.coGrantedDate;
return {
...translations,
caseHasBeenIssued: content.caseHasBeenIssued,
isRespondent,
isSoleApplicant,
hasCOBeenGranted,
};
};
Loading