Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/uar 1635 save resume deselect nocs #1631

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 60 additions & 24 deletions src/controllers/beneficial.owner.type.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,19 @@ import {
} from "../model/beneficial.owner.type.model";
import { isActiveFeature } from '../utils/feature.flag';
import { getUrlWithParamsToPath } from "../utils/url";
import { beneficialOwnersTypeEmptyNOCList } from "../validation/async";
mwejuli-ch marked this conversation as resolved.
Show resolved Hide resolved
import { FormattedValidationErrors, formatValidationError, } from "../middleware/validation.middleware";
import { ValidationError } from "express-validator";
import { BeneficialOwnerIndividualKey } from "../model/beneficial.owner.individual.model";
import { BeneficialOwnerOtherKey } from "../model/beneficial.owner.other.model";
import { BeneficialOwnerGovKey } from "../model/beneficial.owner.gov.model";
import { NonLegalFirmNoc } from "../model/data.types.model";

export const get = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.debugRequest(req, `${req.method} ${req.route.path}`);

const appData: ApplicationData = await getApplicationData(req.session);
const requiresTrusts: boolean = checkEntityRequiresTrusts(appData);

logger.infoRequest(req, `${config.BENEFICIAL_OWNER_TYPE_PAGE} requiresTrusts=${requiresTrusts}`);

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_REDIS_REMOVAL)) {
return res.render(config.BENEFICIAL_OWNER_TYPE_PAGE, {
// Even though the value of this feature flag is available in the template via the OE_CONFIGS variable, passing it in
// like this enables unit tests to assert different outcomes, based on whether it is set or not
FEATURE_FLAG_ENABLE_REDIS_REMOVAL: true,
activeSubmissionBasePath: getUrlWithParamsToPath(config.ACTIVE_SUBMISSION_BASE_PATH, req),
backLinkUrl: getUrlWithParamsToPath(config.BENEFICIAL_OWNER_STATEMENTS_WITH_PARAMS_URL, req),
templateName: config.BENEFICIAL_OWNER_TYPE_PAGE,
requiresTrusts,
...appData,
});
}

return res.render(config.BENEFICIAL_OWNER_TYPE_PAGE, {
backLinkUrl: config.BENEFICIAL_OWNER_STATEMENTS_URL,
templateName: config.BENEFICIAL_OWNER_TYPE_PAGE,
requiresTrusts,
...appData,
});
return await renderPage(req, res);
} catch (error) {
logger.errorRequest(req, error);
next(error);
Expand All @@ -57,14 +41,30 @@ export const postSubmit = async (req: Request, res: Response) => {
const appData: ApplicationData = await getApplicationData(req.session);
const requiresTrusts: boolean = checkEntityRequiresTrusts(appData);
let nextPageUrl = config.CHECK_YOUR_ANSWERS_URL;

const errors = await getValidationErrors(appData, req);

if (errors.length) {
return renderPage(req, res, formatValidationError(errors));
}

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_REDIS_REMOVAL)) {
nextPageUrl = getUrlWithParamsToPath(config.CHECK_YOUR_ANSWERS_WITH_PARAMS_URL, req);
}

if (requiresTrusts) {
nextPageUrl = isActiveFeature(config.FEATURE_FLAG_ENABLE_TRUSTS_WEB)
? getTrustLandingUrl(appData, req)
: config.TRUST_INFO_URL;
}

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC)) {
logger.debugRequest(req, "Removing old NOCs");
appData?.[BeneficialOwnerIndividualKey]?.forEach(boi => { delete boi[NonLegalFirmNoc]; });
appData?.[BeneficialOwnerOtherKey]?.forEach(boo => { delete boo[NonLegalFirmNoc]; });
appData?.[BeneficialOwnerGovKey]?.forEach(bog => { delete bog[NonLegalFirmNoc]; });
}

return res.redirect(nextPageUrl);
};

Expand Down Expand Up @@ -96,3 +96,39 @@ const getNextPage = (req: Request): string => {
return config.MANAGING_OFFICER_URL;
}
};

// Get validation errors that depend on an asynchronous request
const getValidationErrors = async (appData: ApplicationData, req: Request): Promise<ValidationError[]> => {
const beneficialOwnersTypeEmptyNOCListErrors = await beneficialOwnersTypeEmptyNOCList(req, appData);

return [...beneficialOwnersTypeEmptyNOCListErrors];
};

const renderPage = async (req: Request, res: Response, errors?: FormattedValidationErrors) => {
const appData: ApplicationData = await getApplicationData(req.session);
const requiresTrusts: boolean = checkEntityRequiresTrusts(appData);

logger.infoRequest(req, `${config.BENEFICIAL_OWNER_TYPE_PAGE} requiresTrusts=${requiresTrusts}`);

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_REDIS_REMOVAL)) {
return res.render(config.BENEFICIAL_OWNER_TYPE_PAGE, {
// Even though the value of this feature flag is available in the template via the OE_CONFIGS variable, passing it in
// like this enables unit tests to assert different outcomes, based on whether it is set or not
FEATURE_FLAG_ENABLE_REDIS_REMOVAL: true,
activeSubmissionBasePath: getUrlWithParamsToPath(config.ACTIVE_SUBMISSION_BASE_PATH, req),
backLinkUrl: getUrlWithParamsToPath(config.BENEFICIAL_OWNER_STATEMENTS_WITH_PARAMS_URL, req),
templateName: config.BENEFICIAL_OWNER_TYPE_PAGE,
requiresTrusts,
...appData,
errors,
});
}

return res.render(config.BENEFICIAL_OWNER_TYPE_PAGE, {
backLinkUrl: config.BENEFICIAL_OWNER_STATEMENTS_URL,
templateName: config.BENEFICIAL_OWNER_TYPE_PAGE,
requiresTrusts,
...appData,
errors,
});
};
8 changes: 0 additions & 8 deletions src/controllers/shared/common.resume.submission.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { getOverseasEntity } from "../../service/overseas.entities.service";
import {
HasSoldLandKey,
ID,
NonLegalFirmNoc,
IsSecureRegisterKey,
OverseasEntityKey,
Transactionkey
Expand Down Expand Up @@ -111,12 +110,5 @@ const setWebApplicationData = (session: Session, appData: ApplicationData, trans
mapTrustApiReturnModelToWebModel(appData);
}

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC) && (!appData.entity_number)) {
console.debug("Removing old NOCs");
appData[BeneficialOwnerIndividualKey].forEach(boi => { delete boi[NonLegalFirmNoc]; });
appData[BeneficialOwnerOtherKey].forEach(boo => { delete boo[NonLegalFirmNoc]; });
appData[BeneficialOwnerGovKey].forEach(bog => { delete bog[NonLegalFirmNoc]; });
}

setExtraData(session, appData);
};
57 changes: 43 additions & 14 deletions src/controllers/update/beneficial.owner.type.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import { saveAndContinue } from "../../utils/save.and.continue";
import { Session } from "@companieshouse/node-session-handler";
import { validationResult } from "express-validator/src/validation-result";
import { FormattedValidationErrors, formatValidationError } from "../../middleware/validation.middleware";
import { isActiveFeature } from "../../utils/feature.flag";
import { NonLegalFirmNoc } from "../../model/data.types.model";
import { beneficialOwnersTypeEmptyNOCList } from "../../validation/async";
import { ValidationError } from "express-validator";

type BeneficialOwnerTypePageProperties = {
backLinkUrl: string;
Expand Down Expand Up @@ -62,21 +66,8 @@ const getPageProperties = async (
export const get = async (req: Request, res: Response, next: NextFunction) => {
try {
logger.debugRequest(req, `${req.method} ${req.route.path}`);
const appData: ApplicationData = await getApplicationData(req.session);

const checkIsRedirect = checkAndReviewBeneficialOwner(appData);
if (checkIsRedirect && checkIsRedirect !== "") {
return res.redirect(checkIsRedirect);
}

const checkMoRedirect = checkAndReviewManagingOfficers(appData);
if (checkMoRedirect){
return res.redirect(checkMoRedirect);
}

const pageProps = await getPageProperties(req);

return res.render(pageProps.templateName, pageProps);
return await renderOrRedirectPage(req, res);
} catch (error) {
logger.errorRequest(req, error);
next(error);
Expand All @@ -103,6 +94,12 @@ export const postSubmit = async (req: Request, res: Response, next: NextFunction

const appData: ApplicationData = await getApplicationData(req.session);

const errors = await getValidationErrors(appData, req);

if (errors.length) {
return renderOrRedirectPage(req, res, formatValidationError(errors));
}

if (!appData.update?.trust_data_fetched) {
const session = req.session as Session;
await retrieveTrustData(req, appData);
Expand All @@ -116,6 +113,13 @@ export const postSubmit = async (req: Request, res: Response, next: NextFunction
moveReviewableTrustsIntoReview(appData);
resetReviewStatusOnAllTrustsToBeReviewed(appData);

if (isActiveFeature(config.FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC)) {
logger.debugRequest(req, "Removing old NOCs");
appData?.[BeneficialOwnerIndividualKey]?.forEach(boi => { delete boi[NonLegalFirmNoc]; });
appData?.[BeneficialOwnerOtherKey]?.forEach(boo => { delete boo[NonLegalFirmNoc]; });
appData?.[BeneficialOwnerGovKey]?.forEach(bog => { delete bog[NonLegalFirmNoc]; });
}

if (hasTrustsToReview(appData)) {
return res.redirect(config.UPDATE_MANAGE_TRUSTS_INTERRUPT_URL);
}
Expand Down Expand Up @@ -152,3 +156,28 @@ const getNextPage = (beneficialOwnerTypeChoices: BeneficialOwnerTypeChoice | Man
return config.UPDATE_BENEFICIAL_OWNER_INDIVIDUAL_URL;
}
};

// Get validation errors that depend on an asynchronous request
const getValidationErrors = async (appData: ApplicationData, req: Request): Promise<ValidationError[]> => {
const beneficialOwnersTypeEmptyNOCListErrors = await beneficialOwnersTypeEmptyNOCList(req, appData);

return [...beneficialOwnersTypeEmptyNOCListErrors];
};

const renderOrRedirectPage = async (req: Request, res: Response, errors?: FormattedValidationErrors) => {
const appData: ApplicationData = await getApplicationData(req.session);

const checkIsRedirect = checkAndReviewBeneficialOwner(appData);
if (checkIsRedirect && checkIsRedirect !== "") {
return res.redirect(checkIsRedirect);
}

const checkMoRedirect = checkAndReviewManagingOfficers(appData);
if (checkMoRedirect){
return res.redirect(checkMoRedirect);
}

const pageProps = await getPageProperties(req, errors);

return res.render(pageProps.templateName, pageProps);
};
57 changes: 57 additions & 0 deletions src/validation/async/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import { checkCeasedDateOnOrAfterDateOfBirth, checkCeasedDateOnOrAfterTrustCreat
import { dateContext } from '../../validation/fields/helper/date.validation.helper';
import { checkDatePreviousToFilingDate } from '../../validation/custom.validation';
import isAllowedUrls from './isAllowedUrls';
import { BeneficialOwnerIndividualKey } from '../../model/beneficial.owner.individual.model';
import { BeneficialOwnerOtherKey } from '../../model/beneficial.owner.other.model';
import { BeneficialOwnerGovKey } from '../../model/beneficial.owner.gov.model';
import { isActiveFeature } from '../../utils/feature.flag';

export const checkTrustIndividualCeasedDate = async (appData: ApplicationData, req: Request): Promise<ValidationError[]> => {
const allowedUrls = [
Expand Down Expand Up @@ -152,3 +156,56 @@ export const filingPeriodTrustStartDateValidations = async (req: Request) => is_

export const filingPeriodTrustCeaseDateValidations = async (req: Request) => is_date_within_filing_period_trusts(req, historicalBOEndDateContext, ErrorMessages.CEASED_DATE_BEFORE_FILING_DATE);

export const beneficialOwnersTypeEmptyNOCList = async (req: Request, appData: ApplicationData): Promise<ValidationError[]> => {
const allowedUrls = [
[config.REGISTER_AN_OVERSEAS_ENTITY_URL, config.BENEFICIAL_OWNER_TYPE_PAGE],
[config.UPDATE_AN_OVERSEAS_ENTITY_URL, config.UPDATE_BENEFICIAL_OWNER_TYPE_PAGE]
];

const allowed: boolean = isAllowedUrls(allowedUrls, req);

const errors: ValidationError[] = [];

if (!allowed) {
return errors;
}

try {
const isActive = isActiveFeature(config.FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC);
mwestacott marked this conversation as resolved.
Show resolved Hide resolved

const boiList = checkIfBeneficialOwnerHasNOC(appData?.[BeneficialOwnerIndividualKey] ?? [], isActive);
const booList = checkIfBeneficialOwnerHasNOC(appData?.[BeneficialOwnerOtherKey] ?? [], isActive);
const bogList = checkIfBeneficialOwnerHasNOC(appData?.[BeneficialOwnerGovKey] ?? [], isActive);

const boList: string[] = [...boiList, ...booList, ...bogList];

if (boList.length) {
throw new Error(`${ErrorMessages.MISSING_NATURE_OF_CONTROL} ${boList.join(", ")}`);
}

return errors;
} catch (error) {
errors.push({
value: '',
msg: error.message,
param: 'beneficial_owner_list',
location: 'body',
});

return errors;
}
};

const checkIfBeneficialOwnerHasNOC = (beneficialOwner, isActive: boolean): string[] => {
return beneficialOwner?.filter(bo =>
!bo?.beneficial_owner_nature_of_control_types?.length &&
mwestacott marked this conversation as resolved.
Show resolved Hide resolved
!bo?.trustees_nature_of_control_types?.length &&
!bo?.non_legal_firm_control_nature_of_control_types?.length &&
!bo?.trust_control_nature_of_control_types?.length &&
!bo?.owner_of_land_person_nature_of_control_jurisdictions?.length &&
!bo?.owner_of_land_other_entity_nature_of_control_jurisdictions?.length &&
((!isActive && !bo?.non_legal_firm_members_nature_of_control_types?.length)
|| (isActive && (bo?.non_legal_firm_members_nature_of_control_types?.length || !bo?.non_legal_firm_members_nature_of_control_types?.length)))
).map(bo => bo?.first_name ?? bo?.name);
};

1 change: 1 addition & 0 deletions src/validation/error.messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export enum ErrorMessages {
START_DATE_MUST_BE_AFTER_DOB = "Start date must be on or after date of birth",
UNABLE_TO_RETRIEVE_ENTITY_NUMBER = "Unable to retrieve entity number",
UNABLE_TO_RETRIEVE_EXPECTED_DATE = "Unable to retrieve the entity's expected date",
MISSING_NATURE_OF_CONTROL = "Natures of control options may have changed since you the last time you saved your update statement. Select Change for each beneficial owner to check their natures of control are correct:",
mwestacott marked this conversation as resolved.
Show resolved Hide resolved

// Public Register
PUBLIC_REGISTER_NAME = "Enter the name of the register",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,12 @@ describe("BENEFICIAL OWNER TYPE controller on resume", () => {

describe("POST Submit", () => {
test(`redirects to the trust interrupt page with entity_number null on resume`, async () => {
mockIsActiveFeature.mockReturnValueOnce(true); // For FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC
mockIsActiveFeature.mockReturnValueOnce(false); // For FEATURE_FLAG_ENABLE_REDIS_REMOVAL
mockIsActiveFeature.mockReturnValueOnce(true);
mockIsActiveFeature.mockReturnValueOnce(true); // For FEATURE_FLAG_ENABLE_TRUSTS_WEB
mockIsActiveFeature.mockReturnValueOnce(false); // For FEATURE_FLAG_ENABLE_REDIS_REMOVAL
mockIsActiveFeature.mockReturnValueOnce(true); // For FEATURE_FLAG_ENABLE_PROPERTY_OR_LAND_OWNER_NOC

const appDataMock = {
...APPLICATION_DATA_MOCK,
[EntityNumberKey]: null,
Expand Down
Loading