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

feat(DTFS2-7494): hide change button when amendment underway #4204

Open
wants to merge 4 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
7 changes: 5 additions & 2 deletions gef-ui/server/controllers/application-details/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const startCase = require('lodash/startCase');
const { DEAL_TYPE, timeZoneConfig, DEAL_STATUS } = require('@ukef/dtfs2-common');
const { DEAL_TYPE, timeZoneConfig, DEAL_STATUS, PORTAL_AMENDMENT_UNDERWAY_STATUSES } = require('@ukef/dtfs2-common');
const api = require('../../services/api');
const { canUpdateUnissuedFacilitiesCheck } = require('./canUpdateUnissuedFacilitiesCheck');
const {
Expand Down Expand Up @@ -264,7 +264,10 @@ const applicationDetails = async (req, res, next) => {
params.link += '/unissued-facilities';
}

params.canIssuedFacilitiesBeAmended = canUserAmendIssuedFacilities(application.submissionType, application.status, userRoles);
const amendmentsUnderwayOnDeal = await api.getAmendmentsOnDeal({ dealId, statuses: PORTAL_AMENDMENT_UNDERWAY_STATUSES, userToken });

params.canIssuedFacilitiesBeAmended =
canUserAmendIssuedFacilities(application.submissionType, application.status, userRoles) && !amendmentsUnderwayOnDeal.length;

return res.render(`partials/${partial}.njk`, params);
} catch (error) {
Expand Down
25 changes: 24 additions & 1 deletion gef-ui/server/controllers/application-details/index.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cloneDeep } from 'lodash';
import { DEAL_STATUS, DEAL_SUBMISSION_TYPE, DEAL_TYPE, FACILITY_TYPE, isPortalFacilityAmendmentsFeatureFlagEnabled, ROLES } from '@ukef/dtfs2-common';
import { aPortalFacilityAmendment } from '@ukef/dtfs2-common/mock-data-backend';

Check failure on line 3 in gef-ui/server/controllers/application-details/index.test.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

gef-ui/server/controllers/application-details/index.test.js#L3

Unable to resolve path to module '@ukef/dtfs2-common/mock-data-backend'.
import { applicationDetails, postApplicationDetails } from '.';
import api from '../../services/api';
import { NON_MAKER_ROLES } from '../../../test-helpers/common-role-lists';
Expand All @@ -23,6 +24,7 @@
let mockFacilityResponse;
let mockFacilitiesResponse;
let mockUserResponse;
const mockGetAmendmentsOnDealResponse = [];

beforeEach(() => {
mockResponse = MOCKS.MockResponse();
Expand All @@ -35,6 +37,7 @@
api.getApplication.mockResolvedValue(mockApplicationResponse);
api.getFacilities.mockResolvedValue(mockFacilitiesResponse);
api.getUserDetails.mockResolvedValue(mockUserResponse);
api.getAmendmentsOnDeal.mockResolvedValue(mockGetAmendmentsOnDealResponse);
mockRequest.flash = mockSuccessfulFlashResponse();
});

Expand Down Expand Up @@ -334,7 +337,7 @@
);
});

it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=true when the deal status is ${DEAL_STATUS.UKEF_ACKNOWLEDGED} and the submission type is valid`, async () => {
it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=true when the deal status is ${DEAL_STATUS.UKEF_ACKNOWLEDGED}, the submission type is valid and there are no amendments underway on the deal`, async () => {
api.getFacilities.mockResolvedValue(MOCKS.MockFacilityResponseNotChangedIssued);
jest.mocked(isPortalFacilityAmendmentsFeatureFlagEnabled).mockReturnValueOnce(true);

Expand Down Expand Up @@ -429,6 +432,26 @@
);
});

it(`renders 'application-preview' with canIssuedFacilitiesBeAmended=false when there is an amendment already underway on the deal`, async () => {
api.getFacilities.mockResolvedValue(MOCKS.MockFacilityResponseNotChangedIssued);
jest.mocked(isPortalFacilityAmendmentsFeatureFlagEnabled).mockReturnValueOnce(true);

api.getAmendmentsOnDeal.mockResolvedValueOnce([aPortalFacilityAmendment()]);

mockApplicationResponse.status = DEAL_STATUS.UKEF_ACKNOWLEDGED;
mockApplicationResponse.submissionType = DEAL_SUBMISSION_TYPE.AIN;
api.getApplication.mockResolvedValueOnce(mockApplicationResponse);

await applicationDetails(mockRequest, mockResponse);

expect(mockResponse.render).toHaveBeenCalledWith(
'partials/application-preview.njk',
expect.objectContaining({
canIssuedFacilitiesBeAmended: false,
}),
);
});

it('renders `review-decision` when page requested is `review-decision`', async () => {
mockApplicationResponse.status = CONSTANTS.DEAL_STATUS.UKEF_APPROVED_WITHOUT_CONDITIONS;
api.getApplication.mockResolvedValueOnce(mockApplicationResponse);
Expand Down
1 change: 1 addition & 0 deletions gef-ui/server/services/__mocks__/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ module.exports = {
getAmendment: jest.fn(),
upsertAmendment: jest.fn(),
updateAmendment: jest.fn(),
getAmendmentsOnDeal: jest.fn(),
};
41 changes: 40 additions & 1 deletion gef-ui/server/services/__tests__/api.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { AxiosError, HttpStatusCode } from 'axios';
import { InvalidDealIdError, InvalidFacilityIdError, MOCK_COMPANY_REGISTRATION_NUMBERS } from '@ukef/dtfs2-common';
import {
InvalidDealIdError,
InvalidFacilityIdError,
MOCK_COMPANY_REGISTRATION_NUMBERS,
PORTAL_AMENDMENT_STATUS,
PORTAL_AMENDMENT_UNDERWAY_STATUSES,
} from '@ukef/dtfs2-common';

Check failure on line 8 in gef-ui/server/services/__tests__/api.test.js

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

gef-ui/server/services/__tests__/api.test.js#L8

Unable to resolve path to module '@ukef/dtfs2-common'.
import Axios from '../axios';
import api from '../api';
import CONSTANTS from '../../constants';
Expand Down Expand Up @@ -494,3 +500,36 @@
await expect(returned).rejects.toThrow(InvalidFacilityIdError);
});
});

describe('getAmendmentsOnDeal()', () => {
it(`should return the found amendments`, async () => {
// Arrange
const mockAmendment = { ...new PortalFacilityAmendmentWithUkefIdMockBuilder().build(), status: PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL };
Axios.get.mockResolvedValueOnce({ data: [mockAmendment] });

// Act
const response = await api.getAmendmentsOnDeal({ dealId: validMongoId, userToken, statuses: PORTAL_AMENDMENT_UNDERWAY_STATUSES });

// Assert
expect(response).toEqual([mockAmendment]);
});

it('should throw an error if there is an api error', async () => {
// Arrange
Axios.get.mockRejectedValueOnce(new AxiosError());

// Act
const returned = api.getAmendmentsOnDeal({ dealId: validMongoId, statuses: [PORTAL_AMENDMENT_STATUS.DRAFT], userToken });

// Assert
await expect(returned).rejects.toThrow(AxiosError);
});

it.each(invalidMongoIdTestCases)('should throw an error when given an invalid facility Id', async (invalidMongoId) => {
// Act
const returned = api.getAmendmentsOnDeal({ dealId: invalidMongoId, userToken });

// Assert
await expect(returned).rejects.toThrow(InvalidDealIdError);
});
});
24 changes: 24 additions & 0 deletions gef-ui/server/services/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,29 @@ const getAmendment = async ({ facilityId, amendmentId, userToken }) => {
}
};

/**
* @param {Object} param
* @param {string} param.dealId
* @param {string} param.userToken
* @param {import('@ukef/dtfs2-common').PortalAmendmentStatus[] | undefined} param.statuses
* @returns {Promise<(import('@ukef/dtfs2-common').PortalFacilityAmendmentWithUkefId[])>}>}
*/
const getAmendmentsOnDeal = async ({ dealId, userToken, statuses }) => {
if (!isValidMongoId(dealId)) {
console.error('Invalid deal ID %s', dealId);
throw new InvalidDealIdError(dealId);
}

try {
const response = await Axios.get(`/gef/deals/${dealId}/amendments`, { ...config(userToken), params: { statuses } });

return response.data;
} catch (error) {
console.error('Failed to get the amendments for facilities on deal with id %s: %o', dealId, error);
throw error;
}
};

/**
* @param {Object} param
* @param {string} param.facilityId
Expand Down Expand Up @@ -470,6 +493,7 @@ module.exports = {
deleteFile,
downloadFile,
updateSupportingInformation,
getAmendmentsOnDeal,
getAmendment,
upsertAmendment,
updateAmendment,
Expand Down
4 changes: 2 additions & 2 deletions gef-ui/server/utils/facility-amendments.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Deal } from '../types/deal';
import { Facility } from '../types/facility';

/**
* returns a boolean indicating whether the user can amend issued facilities based on the submission type, deal status and user roles.
* returns a boolean indicating whether the user can amend issued facilities in general for a deal based on the deal submission type, deal status and user roles.
* @param submissionType - the submission type
* @param dealStatus - the deal status
* @param userRoles - a list of the user roles
Expand All @@ -27,7 +27,7 @@ export const canUserAmendIssuedFacilities = (submissionType: DealSubmissionType,
};

/**
* Determines if a user can amend a facility.
* Determines if a user can amend a given facility.
*
* @param facility - The facility to check.
* @param deal - The deal associated with the facility.
Expand Down
14 changes: 14 additions & 0 deletions libs/common/src/constants/amendments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@ export const PORTAL_AMENDMENT_STATUS = {
* This is not displayed in the UI
*/
DRAFT: 'Draft',
/**
* Amendment has been submitted by the maker and is awaiting the checker's approval
*/
READY_FOR_CHECKERS_APPROVAL: "Ready for Checker's approval",
/**
* Checker has requested changes from the maker
*/
FURTHER_MAKERS_INPUT_REQUIRED: "Further Maker's input required",
/**
* Amendment has been acknowledged by the checker
*/
ACKNOWLEDGED: 'Acknowledged',
} as const;

export const PORTAL_AMENDMENT_UNDERWAY_STATUSES = [PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL, PORTAL_AMENDMENT_STATUS.FURTHER_MAKERS_INPUT_REQUIRED];

export const AMENDMENT_QUERIES = {
LATEST_VALUE: 'latest-value',
LATEST_COVER_END_DATE: 'latest-cover-end-date',
Expand Down
7 changes: 7 additions & 0 deletions portal-api/api-tests/v1/gef/amendments/amendment-urls.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { PortalAmendmentStatus } from '@ukef/dtfs2-common';

Check failure on line 1 in portal-api/api-tests/v1/gef/amendments/amendment-urls.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendment-urls.ts#L1

Unable to resolve path to module '@ukef/dtfs2-common'.

export const getAmendmentUrl = ({ facilityId, amendmentId }: { facilityId: string; amendmentId: string }) =>
`/v1/gef/facilities/${facilityId}/amendments/${amendmentId}`;

export const patchAmendmentUrl = ({ facilityId, amendmentId }: { facilityId: string; amendmentId: string }) =>
`/v1/gef/facilities/${facilityId}/amendments/${amendmentId}`;

export const putAmendmentUrl = ({ facilityId }: { facilityId: string }) => `/v1/gef/facilities/${facilityId}/amendments`;

export const getAmendmentsOnDealUrl = ({ dealId, statuses }: { dealId: string; statuses?: PortalAmendmentStatus[] }) => {
const statusFilterQuery = statuses ? `?statuses=${statuses.map((item) => encodeURI(item)).join(',')}` : '';

Check failure on line 12 in portal-api/api-tests/v1/gef/amendments/amendment-urls.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendment-urls.ts#L12

Unsafe argument of type `PortalAmendmentStatus` assigned to a parameter of type `string`.
return `/v1/gef/deals/${dealId}/amendments/${statusFilterQuery}`;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { ObjectId } from 'mongodb';
import {
PORTAL_AMENDMENT_STATUS,
AMENDMENT_TYPES,
AnyObject,
PortalFacilityAmendmentWithUkefId,
Role,
PORTAL_AMENDMENT_UNDERWAY_STATUSES,
} from '@ukef/dtfs2-common';

Check failure on line 9 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L9

Unable to resolve path to module '@ukef/dtfs2-common'.
import { HttpStatusCode } from 'axios';
import app from '../../../../src/createApp';
import testUserCache from '../../../api-test-users';

import ROLES, { MAKER } from '../../../../src/v1/roles/roles';
import { getAmendmentsOnDealUrl } from './amendment-urls';
import createApi from '../../../api';
import { TestUser } from '../../../types/test-user';
import { withRoleAuthorisationTests } from '../../../common-tests/role-authorisation-tests';
import { withClientAuthenticationTests } from '../../../common-tests/client-authentication-tests';

const { as, get } = createApi(app);

const getPortalFacilityAmendmentsOnDealMock = jest.fn() as jest.Mock<Promise<PortalFacilityAmendmentWithUkefId[]>>;

jest.mock('../../../../src/v1/api', () => ({
...jest.requireActual<AnyObject>('../../../../src/v1/api'),
getPortalFacilityAmendmentsOnDeal: () => getPortalFacilityAmendmentsOnDealMock(),
}));

const validDealId = new ObjectId().toString();
const statuses = PORTAL_AMENDMENT_UNDERWAY_STATUSES;

Check failure on line 31 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L31

Unsafe assignment of an error typed value.

const invalidId = 'invalid-id';

describe('/v1/gef/deals/:dealId/amendments', () => {
let testUsers: Awaited<ReturnType<typeof testUserCache.initialise>>;
let aMaker: TestUser;

describe('GET /v1/gef/deals/:dealId/amendments', () => {
beforeEach(() => {
jest.resetAllMocks();
});

beforeAll(async () => {
testUsers = await testUserCache.initialise(app);
aMaker = testUsers().withRole(MAKER).one() as TestUser;
MarRobSoftwire marked this conversation as resolved.
Show resolved Hide resolved
});

afterAll(() => {
jest.resetAllMocks();
});

afterAll(() => {
jest.resetAllMocks();
});

withClientAuthenticationTests({
makeRequestWithoutAuthHeader: () => get(getAmendmentsOnDealUrl({ dealId: validDealId })),
makeRequestWithAuthHeader: (authHeader: string) =>
get(getAmendmentsOnDealUrl({ dealId: validDealId, statuses }), { headers: { Authorization: authHeader } }),
});

withRoleAuthorisationTests({
allowedRoles: Object.values(ROLES),
getUserWithRole: (role: Role) => testUsers().withRole(role).one() as TestUser,
makeRequestAsUser: (user: TestUser) => as(user).get(getAmendmentsOnDealUrl({ dealId: validDealId, statuses })),
successStatusCode: HttpStatusCode.Ok,
});

it(`should return a ${HttpStatusCode.BadRequest} response when deal id path param is invalid`, async () => {
// Arrange
const url = getAmendmentsOnDealUrl({ dealId: invalidId, statuses });

// Act
const response = await as(aMaker).get(url);

Check failure on line 75 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L75

Unsafe assignment of an error typed value.

// Assert
expect(response.status).toEqual(HttpStatusCode.BadRequest);
});

it(`should return a ${HttpStatusCode.Ok} response and the amendment for an authenticated user`, async () => {
const amendmentId = new ObjectId().toString();

Check failure on line 82 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L82

Unsafe assignment of an error typed value.
const facilityId = new ObjectId().toString();
const dealId = new ObjectId().toString();

Check failure on line 84 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L84

Unsafe assignment of an error typed value.

Check failure on line 84 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L84

Unsafe member access .toString on an `error` typed value.

// Arrange
const amendment: PortalFacilityAmendmentWithUkefId = {
amendmentId,
facilityId,
dealId,

Check failure on line 90 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L90

Unsafe assignment of an error typed value.
type: AMENDMENT_TYPES.PORTAL,

Check failure on line 91 in portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

portal-api/api-tests/v1/gef/amendments/amendmentsOnDeal.get.api-test.ts#L91

Unsafe assignment of an error typed value.
ukefFacilityId: '123',
createdAt: 1702061978881,
updatedAt: 1702061978881,
status: PORTAL_AMENDMENT_STATUS.READY_FOR_CHECKERS_APPROVAL,
eligibilityCriteria: { version: 1, criteria: [] },
createdBy: {
username: aMaker.username,
name: aMaker.firstname,
email: aMaker.email,
},
};

jest.mocked(getPortalFacilityAmendmentsOnDealMock).mockResolvedValue([amendment]);
const url = getAmendmentsOnDealUrl({ dealId, statuses });

// Act
const response = await as(aMaker).get(url);

// Assert
expect(response.status).toEqual(HttpStatusCode.Ok);
expect(response.body).toEqual([amendment]);
});
});
});
23 changes: 23 additions & 0 deletions portal-api/src/v1/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,28 @@ const getPortalFacilityAmendment = async (facilityId, amendmentId) => {
}
};

/**
* Gets portal facility amendments on the deal filtered by status
* @param {string} dealId - id of the facility to amend
* @param {import('@ukef/dtfs2-common').PortalAmendmentStatus[] | undefined} statuses - an optional array of statuses to filter the amendments by
* @returns {Promise<(import('@ukef/dtfs2-common').PortalFacilityAmendmentWithUkefId[])>} - the amendments on the deal with a matching status
*/
const getPortalFacilityAmendmentsOnDeal = async (dealId, statuses) => {
try {
const response = await axios({
method: 'get',
url: `${DTFS_CENTRAL_API_URL}/v1/portal/deals/${dealId}/amendments`,
params: { statuses },
headers: headers.central,
MarRobSoftwire marked this conversation as resolved.
Show resolved Hide resolved
});

return response.data;
} catch (error) {
console.error('Error getting portal facility amendments on deal with id %s: %o', dealId, error);
throw error;
}
};

/**
* Upserts a draft amendment for a portal facility in the database.
* @param {Object} params
Expand Down Expand Up @@ -751,6 +773,7 @@ module.exports = {
saveFeeRecordCorrection,
getFeeRecordCorrectionTransientFormData,
getPortalFacilityAmendment,
getPortalFacilityAmendmentsOnDeal,
putPortalFacilityAmendment,
getFeeRecordCorrectionReview,
patchPortalFacilityAmendment,
Expand Down
Loading
Loading