Skip to content

Commit 362ec69

Browse files
authored
feat(FN-3504): apply cover percentage to initial utilisation (#3695)
1 parent ec78533 commit 362ec69

File tree

3 files changed

+97
-27
lines changed

3 files changed

+97
-27
lines changed

dtfs-central-api/api-tests/v1/utilisation-reports/post-upload-utilisation-report.api-test.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { testApi } from '../../test-api';
1818
import { SqlDbHelper } from '../../sql-db-helper';
1919
import { mongoDbClient } from '../../../src/drivers/db-client';
2020
import { wipe } from '../../wipeDB';
21-
import { aUtilisationReportRawCsvData, aPortalUser, aFacility, aBank } from '../../../test-helpers';
21+
import { aUtilisationReportRawCsvData, aPortalUser, aFacility, aBank, aTfmFacility } from '../../../test-helpers';
2222
import { PostUploadUtilisationReportRequestBody } from '../../../src/v1/controllers/utilisation-report-service/post-upload-utilisation-report.controller';
2323

2424
console.error = jest.fn();
@@ -152,6 +152,58 @@ describe(`POST ${getUrl()}`, () => {
152152
expect(facilityIdExists).toEqual([true, true, true]);
153153
});
154154

155+
it('should calculate and save initial utilisation and fixed fee using facility values from facility creation', async () => {
156+
// Arrange
157+
const ukefFacilityId = '11111111';
158+
const reportData: UtilisationReportRawCsvData[] = [
159+
{
160+
...aUtilisationReportRawCsvData(),
161+
'ukef facility id': ukefFacilityId,
162+
},
163+
];
164+
165+
const tfmFacility: WithoutId<TfmFacility> = {
166+
...aTfmFacility(),
167+
facilitySnapshot: {
168+
...aTfmFacility().facilitySnapshot,
169+
ukefFacilityId,
170+
coverPercentage: 80,
171+
coverStartDate: new Date('2024-01-01'),
172+
// 366 days after cover start date because 2024 is a leap year
173+
coverEndDate: new Date('2025-01-01'),
174+
value: 500000,
175+
interestPercentage: 5,
176+
dayCountBasis: 360,
177+
},
178+
};
179+
180+
const tfmFacilitiesCollection = await mongoDbClient.getCollection(MONGO_DB_COLLECTIONS.TFM_FACILITIES);
181+
await tfmFacilitiesCollection.insertOne(tfmFacility);
182+
183+
const payload: PostUploadUtilisationReportRequestBody = { ...aValidPayload(), reportData };
184+
185+
// Act
186+
const response = await testApi.post(payload).to(getUrl());
187+
188+
// Assert
189+
expect(response.status).toEqual(HttpStatusCode.Created);
190+
191+
const facilityUtilisationData = await SqlDbHelper.manager.findOneBy(FacilityUtilisationDataEntity, { id: ukefFacilityId });
192+
expect(facilityUtilisationData).not.toBeNull();
193+
/**
194+
* Initial utilisation is 10% of the facility value * (cover percentage / 100) thus,
195+
* utilisation = 0.1 * 500000 * (80 / 100)
196+
* = 40000
197+
*/
198+
expect(facilityUtilisationData?.utilisation).toEqual(40000);
199+
/**
200+
* Initial fixed fee = initial utilisation * bank margin rate * interest * days in cover period / day count basis
201+
* = 40000 * 0.9 * (5 / 100) * 366 / 360
202+
* = 1830
203+
*/
204+
expect(facilityUtilisationData?.fixedFee).toEqual(1830);
205+
});
206+
155207
it('creates an entry in the AzureFileInfo table', async () => {
156208
// Act
157209
const response = await testApi.post(aValidPayload()).to(getUrl());
@@ -211,7 +263,7 @@ describe(`POST ${getUrl()}`, () => {
211263
});
212264
});
213265

214-
it('creates a new FacilityUtilisationData row using the report reportPeriod if the report data has a facility id which does not already exist', async () => {
266+
it('creates a new FacilityUtilisationData row using the previous reportPeriod if the report data has a facility id which does not already exist', async () => {
215267
// Arrange
216268
await SqlDbHelper.deleteAll();
217269

dtfs-central-api/src/services/state-machines/utilisation-report/event-handlers/helpers/calculate-initial-utilisation-and-fixed-fee.test.ts

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
1-
import { calculateInitialUtilisation } from '@ukef/dtfs2-common';
1+
import * as dtfsCommon from '@ukef/dtfs2-common';
22
import { calculateInitialUtilisationAndFixedFee, parseDate, hasRequiredValues, RequiredParams } from './calculate-initial-utilisation-and-fixed-fee';
33
import { TfmFacilitiesRepo } from '../../../../../repositories/tfm-facilities-repo';
44
import { aTfmFacility } from '../../../../../../test-helpers';
5-
import { calculateInitialFixedFee } from './calculate-initial-fixed-fee';
6-
import { calculateUkefShareOfUtilisation } from '../../../../../helpers';
5+
import * as fixedFeeHelpers from './calculate-initial-fixed-fee';
6+
7+
jest.mock('./calculate-initial-fixed-fee');
8+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
9+
jest.mock('@ukef/dtfs2-common', () => ({
10+
...jest.requireActual('@ukef/dtfs2-common'),
11+
calculateDrawnAmount: jest.fn(),
12+
}));
713

814
describe('helpers/calculate-initial-utilisation-and-fixed-fee', () => {
915
describe('parseDate', () => {
@@ -145,26 +151,40 @@ describe('helpers/calculate-initial-utilisation-and-fixed-fee', () => {
145151
findOneByUkefFacilityIdSpy.mockResolvedValue(facility);
146152
});
147153

148-
it('should return a value for utilisation and fixed fee', async () => {
154+
it('should set initial utilisation to drawn amount rounded to 2 decimal places', async () => {
155+
// Arrange
156+
const drawnAmount = 12345.678;
157+
const drawnAmountRoundedToTwoDecimalPlaces = 12345.68;
158+
const calculateDrawnAmountSpy = jest.spyOn(dtfsCommon, 'calculateDrawnAmount').mockReturnValue(drawnAmount);
159+
jest.mocked(fixedFeeHelpers.calculateInitialFixedFee).mockReturnValue(999.99);
160+
161+
// Act
149162
const result = await calculateInitialUtilisationAndFixedFee(facilityId);
150163

151-
const { value, coverStartDate, coverEndDate, interestPercentage, dayCountBasis } = facility.facilitySnapshot;
164+
// Assert
165+
expect(calculateDrawnAmountSpy).toHaveBeenCalledWith(facility.facilitySnapshot.value, facility.facilitySnapshot.coverPercentage);
166+
expect(result.utilisation).toEqual(drawnAmountRoundedToTwoDecimalPlaces);
167+
});
152168

153-
const utilisation = calculateInitialUtilisation(value);
154-
const ukefShareOfUtilisation = calculateUkefShareOfUtilisation(utilisation, facility.facilitySnapshot.coverPercentage);
169+
it('should calculate and return the initial fixed fee', async () => {
170+
// Arrange
171+
const drawnAmount = 12345.678;
172+
const drawnAmountRoundedToTwoDecimalPlaces = 12345.68;
173+
jest.mocked(dtfsCommon.calculateDrawnAmount).mockReturnValue(drawnAmount);
174+
const calculateInitialFixedFeeSpy = jest.spyOn(fixedFeeHelpers, 'calculateInitialFixedFee').mockReturnValue(999.99);
155175

156-
const expected = {
157-
fixedFee: calculateInitialFixedFee({
158-
ukefShareOfUtilisation,
159-
coverStartDate: parseDate(coverStartDate),
160-
coverEndDate: parseDate(coverEndDate),
161-
interestPercentage,
162-
dayCountBasis,
163-
}),
164-
utilisation,
165-
};
176+
// Act
177+
const result = await calculateInitialUtilisationAndFixedFee(facilityId);
166178

167-
expect(result).toEqual(expected);
179+
// Assert
180+
expect(calculateInitialFixedFeeSpy).toHaveBeenCalledWith({
181+
ukefShareOfUtilisation: drawnAmountRoundedToTwoDecimalPlaces,
182+
coverStartDate: facility.facilitySnapshot.coverStartDate,
183+
coverEndDate: facility.facilitySnapshot.coverEndDate,
184+
interestPercentage: facility.facilitySnapshot.interestPercentage,
185+
dayCountBasis: facility.facilitySnapshot.dayCountBasis,
186+
});
187+
expect(result.fixedFee).toEqual(999.99);
168188
});
169189
});
170190
});

dtfs-central-api/src/services/state-machines/utilisation-report/event-handlers/helpers/calculate-initial-utilisation-and-fixed-fee.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { calculateInitialUtilisation, isValidDate } from '@ukef/dtfs2-common';
1+
import { calculateDrawnAmount, isValidDate } from '@ukef/dtfs2-common';
2+
import Big from 'big.js';
23
import { TfmFacilitiesRepo } from '../../../../../repositories/tfm-facilities-repo';
34
import { calculateInitialFixedFee } from './calculate-initial-fixed-fee';
4-
import { calculateUkefShareOfUtilisation } from '../../../../../helpers';
55

66
export type RequiredParams = {
77
value?: number | null;
@@ -70,12 +70,10 @@ export const calculateInitialUtilisationAndFixedFee = async (facilityId: string)
7070
throw new Error(`TFM facility values for ${facilityId} are missing`);
7171
}
7272

73-
const utilisation = calculateInitialUtilisation(value);
74-
75-
const ukefShareOfUtilisation = calculateUkefShareOfUtilisation(utilisation, coverPercentage);
73+
const ukefShareOfInitialUtilisation = new Big(calculateDrawnAmount(value, coverPercentage)).round(2).toNumber();
7674

7775
const fixedFee = calculateInitialFixedFee({
78-
ukefShareOfUtilisation,
76+
ukefShareOfUtilisation: ukefShareOfInitialUtilisation,
7977
coverStartDate: parseDate(coverStartDate),
8078
coverEndDate: parseDate(coverEndDate),
8179
interestPercentage,
@@ -84,6 +82,6 @@ export const calculateInitialUtilisationAndFixedFee = async (facilityId: string)
8482

8583
return {
8684
fixedFee,
87-
utilisation,
85+
utilisation: ukefShareOfInitialUtilisation,
8886
};
8987
};

0 commit comments

Comments
 (0)