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

DTSCCI-1496 calculate claim interest endpoint #5537

Draft
wants to merge 21 commits into
base: feature/cui-judgement
Choose a base branch
from
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ civil-service:
java:
applicationPort: 4000
releaseNameOverride: ${SERVICE_NAME}-civil-service
image: 'hmctspublic.azurecr.io/civil/service:latest'
image: 'hmctspublic.azurecr.io/civil/service:pr-6116'
imagePullPolicy: Always
ingressHost: ${SERVICE_NAME}-civil-service.preview.platform.hmcts.net
devcpuRequests: 1000m
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ civil-service:
java:
applicationPort: 4000
releaseNameOverride: ${SERVICE_NAME}-civil-service
image: 'hmctspublic.azurecr.io/civil/service:latest'
image: 'hmctspublic.azurecr.io/civil/service:pr-6116'
imagePullPolicy: Always
ingressHost: ${SERVICE_NAME}-civil-service.preview.platform.hmcts.net
devcpuRequests: 500m
Expand Down Expand Up @@ -225,7 +225,7 @@ ccd:
disabled: false
keyVaults: []
ingressHost: ccd-data-store-api-${SERVICE_NAME}.preview.platform.hmcts.net

am-role-assignment-service:
java:
releaseNameOverride: ${SERVICE_NAME}-am-role-assignment-service
Expand Down
11 changes: 11 additions & 0 deletions src/main/app/client/civilServiceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
CIVIL_SERVICE_UPDATE_TASK_STATUS_URL,
CIVIL_SERVICE_GENERAL_APPLICATION_FEE_URL,
CIVIL_SERVICE_GA_NOTIFICATION_LIST_URL,
CIVIL_SERVICE_CLAIM_CALCULATE_INTEREST,
} from './civilServiceUrls';
import {FeeRange, FeeRanges} from 'common/models/feeRange';
import {plainToInstance} from 'class-transformer';
Expand Down Expand Up @@ -388,6 +389,16 @@ export class CivilServiceClient {
}
}

async calculateClaimInterest(claim: ClaimUpdate): Promise<number> {
try {
const response = await this.client.post(CIVIL_SERVICE_CLAIM_CALCULATE_INTEREST, claim, {headers: {'Content-Type': 'application/json'}});
return response.data as number;
} catch (err: unknown) {
logger.error('Error when calculating extended response deadline');
throw err;
}
}

async calculateExtendedResponseDeadline(extendedDeadline: Date, plusDays: number, req: AppRequest): Promise<Date> {
const config = this.getConfig(req);
try {
Expand Down
1 change: 1 addition & 0 deletions src/main/app/client/civilServiceUrls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const CIVIL_SERVICE_UPLOAD_DOCUMENT_URL = `${CIVIL_SERVICE_DOCUMENT_URL}g
export const CIVIL_SERVICE_CLAIMANT = `${CIVIL_SERVICE_CASES_URL}claimant/`;
export const CIVIL_SERVICE_SUBMIT_EVENT = `${CIVIL_SERVICE_CASES_URL}:caseId/citizen/:submitterId/event`;
export const CIVIL_SERVICE_CALCULATE_DEADLINE = `${CIVIL_SERVICE_CASES_URL}response/deadline`;
export const CIVIL_SERVICE_CLAIM_CALCULATE_INTEREST = `${CIVIL_SERVICE_FEES_URL}/claim/calculate-interest`;
export const CIVIL_SERVICE_COURT_LOCATIONS = '/locations/courtLocations';
export const ASSIGN_CLAIM_TO_DEFENDANT =`${CIVIL_SERVICE_ASSIGNMENT_URL}/case/:claimId/DEFENDANT`;
export const CIVIL_SERVICE_AGREED_RESPONSE_DEADLINE_DATE =`${CIVIL_SERVICE_CASES_URL}response/agreeddeadline/:claimId`;
Expand Down
10 changes: 9 additions & 1 deletion src/main/common/models/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class Claim {
defendantStatementOfTruth?: StatementOfTruthForm | QualifiedStatementOfTruth;
claimAmountBreakup?: ClaimAmountBreakup[];
totalInterest?: number;
claimInterest?: YesNo;
_claimInterest?: YesNo;
interest?: Interest;
submittedDate?: Date;
issueDate?: Date;
Expand Down Expand Up @@ -849,6 +849,14 @@ export class Claim {
return this.threeWeeksBeforeHearingDateString();
}

get claimInterest(): YesNo {
return this._claimInterest;
}

set claimInterest(claimInterest: YesNo | YesNoUpperCamelCase) {
this._claimInterest = claimInterest ? claimInterest.toLowerCase() as YesNo : undefined;
}

isBetweenSixAndThreeWeeksBeforeHearingDate(): boolean {
const nowDate = new Date(new Date().setHours(0, 0, 0, 0));
const sixWeeksBeforeHearingDate = this.sixWeeksBeforeHearingDate();
Expand Down
53 changes: 13 additions & 40 deletions src/main/common/utils/interestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,25 @@ import {
getNumberOfDaysBetweenTwoDays, isAfter4PM,
} from './dateUtils';
import {InterestClaimFromType} from 'form/models/claimDetails';
import {InterestClaimOptionsType} from 'form/models/claim/interest/interestClaimOptionsType';
import {getLng} from 'common/utils/languageToggleUtils';
import config from 'config';
import {CivilServiceClient} from 'client/civilServiceClient';
import {translateDraftClaimToCCDInterest} from 'services/translation/claim/ccdTranslation';

const civilServiceApiBaseUrl = config.get<string>('services.civilService.url');
const civilServiceClient: CivilServiceClient = new CivilServiceClient(civilServiceApiBaseUrl);

const INTEREST_8 = 8;
const ZERO_INTEREST = 0;

export const getInterestDetails = (claim: Claim) => {
export const getInterestDetails = async (claim: Claim) => {
if (claim?.claimInterest === YesNo.NO) {
return undefined;
}
const interestFromDate = getInterestDateOrIssueDate(claim);
const interestToDate = getInterestToDate(claim);
const numberOfDays = getNumberOfDaysBetweenTwoDays(interestFromDate, interestToDate);
const rate = getInterestRate(claim);
const interest = calculateInterestToDate(claim);
const interest = await calculateInterestToDate(claim);

return {interestFromDate, interestToDate, numberOfDays, interest, rate};
};
Expand Down Expand Up @@ -67,43 +71,12 @@ export const getInterestEndDate = (claim: Claim): Date => {
return interestEndDate;
};

export const calculateInterestToDate = (claim: Claim): number => {
if (claim.interest?.interestClaimOptions === InterestClaimOptionsType.BREAK_DOWN_INTEREST) {
return claim.interest.totalInterest?.amount;
}
else if (claim.interest?.interestClaimOptions === InterestClaimOptionsType.SAME_RATE_INTEREST) {
const interestPercent = getInterestRate(claim);
let interestEndDate = getInterestEndDate(claim);
let interestStartDate;
const startDate = getInterestStartDate(claim);

if (claim.isInterestFromClaimSubmitDate()) {
interestStartDate = isAfter4PM(startDate) ? addDaysToDate(startDate, 1) : startDate;
} else if (claim.isInterestFromASpecificDate()) {
interestStartDate = startDate;
}
interestEndDate = isAfter4PM(interestEndDate) ? addDaysToDate(interestEndDate, 2): addDaysToDate(interestEndDate, 1);
const interest = calculateInterest(
claim.totalClaimAmount,
interestPercent,
interestStartDate,
interestEndDate,
);

return (Math.round(interest * 100) / 100);
}

return ZERO_INTEREST;
};

export const calculateInterest = (amount: number, interest: number, startDate: Date, endDate: Date): number => {
const days = Math.abs(getNumberOfDaysBetweenTwoDays(startDate, endDate));
const interestForPerYear = amount * (interest / 100);
const interestForPerDay = (interestForPerYear / 365).toFixed(2);
return Number(interestForPerDay) * days;
export const calculateInterestToDate = async (claim: Claim): Promise<number> => {
const caseDataInterest = translateDraftClaimToCCDInterest(claim);
return await civilServiceClient.calculateClaimInterest(caseDataInterest);
};

export const getInterestData = (claim: Claim, lang: string) => {
export const getInterestData = async (claim: Claim, lang: string) => {
let interestStrtDate = getInterestStartDate(claim);
const interestEndDate1 = getInterestEndDate(claim);
if (claim.isInterestFromClaimSubmitDate()) {
Expand All @@ -112,7 +85,7 @@ export const getInterestData = (claim: Claim, lang: string) => {
const endDate = isAfter4PM(interestEndDate1) ? addDaysToDate(interestEndDate1, 2) : addDaysToDate(interestEndDate1, 1);
const numberOfDays = Math.abs(getNumberOfDaysBetweenTwoDays(interestStrtDate, endDate));
const interestStartDate = formatDateToFullDate(interestStrtDate, getLng(lang));
const interestToDate = calculateInterestToDate(claim).toFixed(2);
const interestToDate = (await calculateInterestToDate(claim)).toFixed(2);
const interestRate = getInterestRate(claim);
const isBreakDownInterest = claim.isInterestClaimOptionsBreakDownInterest();
const howInterestIsCalculatedReason = isBreakDownInterest ? claim.getHowTheInterestCalculatedReason() : undefined;
Expand Down
4 changes: 2 additions & 2 deletions src/main/modules/claimDetailsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {convertToPoundsFilter} from '../common/utils/currencyFormat';
import {ResponseType} from '../common/form/models/responseType';
import { calculateInterestToDate } from 'common/utils/interestUtils';

export const getTotalAmountWithInterestAndFees = (claim: Claim) => {
export const getTotalAmountWithInterestAndFees = async (claim: Claim) => {
let interestToDate = 0;
if (claim.hasInterest()) {
interestToDate = calculateInterestToDate(claim);
interestToDate = await calculateInterestToDate(claim);
}
return (claim.totalClaimAmount || 0) + interestToDate + (convertToPoundsFilter(claim?.claimFee?.calculatedAmountInPence) || 0);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ claimFeeBreakDownController.get(CLAIM_FEE_BREAKUP, claimFeePaymentGuard, (async
}
const claimFee = convertToPoundsFilter(claim.claimFee?.calculatedAmountInPence);
const hasInterest = claim.claimInterest === YesNo.YES;
const interestAmount = calculateInterestToDate(claim);
const interestAmount = await calculateInterestToDate(claim);
const totalAmount = hasInterest ? (claim.totalClaimAmount + interestAmount + claimFee) : (claim.totalClaimAmount + claimFee);
return res.render(viewPath, {
totalClaimAmount: claim.totalClaimAmount?.toFixed(2),
Expand Down
6 changes: 3 additions & 3 deletions src/main/routes/features/claim/totalAmountController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {getCaseDataFromStore} from 'modules/draft-store/draftStoreService';
import {CLAIM_TOTAL_URL, CLAIMANT_TASK_LIST_URL} from '../../urls';
import {CivilServiceClient} from 'client/civilServiceClient';
import {convertToPoundsFilter} from 'common/utils/currencyFormat';
import {calculateInterestToDate} from 'common/utils/interestUtils';
import {saveClaimFee} from 'services/features/claim/amount/claimFeesService';
import {calculateInterestToDate} from 'common/utils/interestUtils';

const civilServiceApiBaseUrl = config.get<string>('services.civilService.url');
const civilServiceClient: CivilServiceClient = new CivilServiceClient(civilServiceApiBaseUrl);
Expand All @@ -28,12 +28,12 @@ totalAmountController.get(CLAIM_TOTAL_URL, (async (req: AppRequest, res: Respons
let interestToDate = 0;

if (claim.hasInterest()) {
interestToDate = calculateInterestToDate(claim);
interestToDate = await calculateInterestToDate(claim);
}

const form = {
claimAmount: claim.totalClaimAmount?.toFixed(2),
interestToDate,
interestToDate: interestToDate.toFixed(2),
claimFee,
totalClaimAmount: ((claim.totalClaimAmount) + (claimFee) + (interestToDate)).toFixed(2),
hearingAmount,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import {getClaimById} from 'modules/utilityService';
const checkAnswersViewPath = 'features/claimantResponse/ccj/check-answers';
const ccjCheckAnswersController = Router();

function renderView(req: Request, res: Response, form: GenericForm<StatementOfTruthForm> | GenericForm<QualifiedStatementOfTruth>, claim: Claim) {
async function renderView(req: Request, res: Response, form: GenericForm<StatementOfTruthForm> | GenericForm<QualifiedStatementOfTruth>, claim: Claim) {
const lang = req.query.lang ? req.query.lang : req.cookies.lang;
const summarySections = getSummarySections(req.params.id, claim, lang);
const summarySections = await getSummarySections(req.params.id, claim, lang);
const signatureType = form.model?.type;
res.render(checkAnswersViewPath, {
form,
Expand All @@ -35,7 +35,7 @@ ccjCheckAnswersController.get(CCJ_CHECK_AND_SEND_URL,
try {
const claim = await getCaseDataFromStore(generateRedisKey(req as unknown as AppRequest));
const form = new GenericForm(getStatementOfTruth(claim));
renderView(req, res, form, claim);
await renderView(req, res, form, claim);
} catch (error) {
next(error);
}
Expand All @@ -52,7 +52,7 @@ ccjCheckAnswersController.post(CCJ_CHECK_AND_SEND_URL, async (req: AppRequest |
await form.validate();
if (form.hasErrors()) {
const claim = await getClaimById(claimId, req, true);
renderView(req, res, form, claim);
await renderView(req, res, form, claim);
} else {
await saveStatementOfTruth(redisKey, form.model);
await submitClaimantResponse(<AppRequest>req);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import {convertToPoundsFilter} from 'common/utils/currencyFormat';
const judgmentAmountSummaryController = Router();
const judgementAmountSummaryViewPath = 'features/claimantResponse/ccj/judgement-amount-summary';

function renderView(req: AppRequest, res: Response, claim: Claim, lang: string, claimFee: number) {
const judgmentSummaryDetails = getJudgmentAmountSummary(claim, claimFee, lang);
async function renderView(req: AppRequest, res: Response, claim: Claim, lang: string, claimFee: number) {
const judgmentSummaryDetails = await getJudgmentAmountSummary(claim, claimFee, lang);
const claimAmountAccepted: number = claim.hasClaimantAcceptedDefendantAdmittedAmount() ? claim.partialAdmissionPaymentAmount() : claim.totalClaimAmount;
res.render(judgementAmountSummaryViewPath, {
claimAmount: claimAmountAccepted.toFixed(2),
Expand All @@ -29,7 +29,7 @@ judgmentAmountSummaryController.get(CCJ_PAID_AMOUNT_SUMMARY_URL, async (req: App
const lang = req.query.lang ? req.query.lang : req.cookies.lang;
const claim = await getCaseDataFromStore(generateRedisKey(req));
const claimFee = convertToPoundsFilter(claim.claimFee?.calculatedAmountInPence);
renderView(req, res, claim, lang, claimFee);
await renderView(req, res, claim, lang, claimFee);
} catch (error) {
next(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {ChooseHowProceed} from 'common/models/chooseHowProceed';
const judgmentAmountSummaryExtendedController = Router();
const judgementAmountSummaryViewPath = 'features/claimantResponse/ccj/judgement-amount-summary';

function renderView(req: AppRequest, res: Response, claim: Claim, lang: string, claimFee: number) {
const judgmentSummaryDetails = getJudgmentAmountSummary(claim, claimFee, lang);
async function renderView(req: AppRequest, res: Response, claim: Claim, lang: string, claimFee: number) {
const judgmentSummaryDetails = await getJudgmentAmountSummary(claim, claimFee, lang);
const claimAmountAccepted: number = claim.hasClaimantAcceptedDefendantAdmittedAmount() ? claim.partialAdmissionPaymentAmount() : claim.totalClaimAmount;
res.render(judgementAmountSummaryViewPath, {
claimAmount: claimAmountAccepted,
Expand All @@ -40,7 +40,7 @@ judgmentAmountSummaryExtendedController.get(
const claimFee = convertToPoundsFilter(
claim.claimFee?.calculatedAmountInPence,
);
renderView(req, res, claim, lang, claimFee);
await renderView(req, res, claim, lang, claimFee);
} catch (error) {
next(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const claimantResponseCheckAnswersController = Router();
async function renderView(req: AppRequest, res: Response, form: GenericForm<StatementOfTruthForm>, claim: Claim, isCarmApplicable: boolean, isMintiApplicable: boolean) {
const lang = req.query.lang ? req.query.lang : req.cookies.lang;
const claimFee = convertToPoundsFilter(claim.claimFee?.calculatedAmountInPence);
const summarySections = getSummarySections(req.params.id, claim, lang, claimFee, isCarmApplicable, isMintiApplicable);
const summarySections = await getSummarySections(req.params.id, claim, lang, claimFee, isCarmApplicable, isMintiApplicable);

res.render(checkAnswersViewPath, {
form,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ firstContactClaimSummaryController.get(FIRST_CONTACT_CLAIM_SUMMARY_URL,
const bytes = CryptoJS.AES.decrypt(firstContact?.pin, claim.respondent1PinToPostLRspec?.accessCode);
const originalText = bytes.toString(CryptoJS.enc.Utf8);
if (claimId && originalText === YesNo.YES) {
const interestData = getInterestDetails(claim);
const totalAmount = getTotalAmountWithInterestAndFees(claim);
const interestData = await getInterestDetails(claim);
const totalAmount = await getTotalAmountWithInterestAndFees(claim);
const timelineRows = getClaimTimeline(claim, getLng(lang));
const timelinePdfUrl = claim.extractDocumentId() && CASE_TIMELINE_DOCUMENTS_URL.replace(':id', claimId).replace(':documentId', claim.extractDocumentId());
res.render('features/public/firstContact/claim-summary', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,17 @@ claimDetailsController.get(CLAIM_DETAILS_URL, (async (req: AppRequest, res: Resp
const claimId = req.params.id;
const claim = await civilServiceClient.retrieveClaimDetails(claimId, req);
const lang = req.query.lang ? req.query.lang : req.cookies.lang;
const interestData = getInterestDetails(claim);
const totalAmount = getTotalAmountWithInterestAndFees(claim);
const interestData = await getInterestDetails(claim);
const totalAmount = await getTotalAmountWithInterestAndFees(claim);
const timelineRows = getClaimTimeline(claim, getLng(lang));
const timelinePdfUrl = claim.extractDocumentId() && CASE_TIMELINE_DOCUMENTS_URL.replace(':id', req.params.id).replace(':documentId', claim.extractDocumentId());
const claimFormUrl = (isCUIReleaseTwo) ? CASE_DOCUMENT_VIEW_URL : CASE_DOCUMENT_DOWNLOAD_URL;
const sealedClaimPdfUrl = getTheClaimFormUrl(req.params.id, claim, claimFormUrl);
const pageTitle = 'PAGES.CLAIM_DETAILS.PAGE_TITLE_NEW';
const claimDetailsViewPath = (isCUIReleaseTwo) ? claimDetailsViewPathNew : claimDetailsViewPathOld;
claim.totalInterest = interestData.interest;
if (claim.hasInterest()) {
claim.totalInterest = interestData.interest;
}
res.render(claimDetailsViewPath, {
claim, totalAmount, interestData, timelineRows, timelinePdfUrl, sealedClaimPdfUrl,
pageCaption: 'PAGES.CLAIM_DETAILS.THE_CLAIM',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,17 +57,17 @@ import {DashboardNotification} from 'models/dashboard/dashboardNotification';
import {getLng} from 'common/utils/languageToggleUtils';
import {LinKFromValues} from 'models/generalApplication/applicationType';

export const replaceDashboardPlaceholders = (textToReplace: string, claim: Claim, claimId: string, notification?: DashboardNotification, lng?: string, appId?: string): string => {
export const replaceDashboardPlaceholders = async (textToReplace: string, claim: Claim, claimId: string, notification?: DashboardNotification, lng?: string, appId?: string): Promise<string> => {

const valuesMap = setDashboardValues(claim, claimId, notification, lng, appId);
const valuesMap = await setDashboardValues(claim, claimId, notification, lng, appId);
valuesMap.forEach((value: string, key: string) => {
textToReplace = textToReplace?.replace(key, value);
});

return textToReplace;
};

const setDashboardValues = (claim: Claim, claimId: string, notification?: DashboardNotification, lng?: string, appId?: string): Map<string, string> => {
const setDashboardValues = async (claim: Claim, claimId: string, notification?: DashboardNotification, lng?: string, appId?: string): Promise<Map<string, string>> => {

const valuesMap: Map<string, string> = new Map<string, string>();
const daysLeftToRespond = claim?.respondent1ResponseDeadline ? getNumberOfDaysBetweenTwoDays(new Date(), claim.respondent1ResponseDeadline).toString() : '';
Expand Down Expand Up @@ -113,7 +113,7 @@ const setDashboardValues = (claim: Claim, claimId: string, notification?: Dashbo
valuesMap.set('{civilMoneyClaimsTelephoneWelshSpeaker}', civilMoneyClaimsTelephoneWelshSpeaker);
valuesMap.set('{cmcCourtEmailId}', cmcCourtEmailId);
valuesMap.set('{cmcCourtAddress}', getSendFinancialDetailsAddress(getLng(lng)));
valuesMap.set('{fullAdmitPayImmediatelyPaymentAmount}', getTotalAmountWithInterestAndFees(claim).toString());
valuesMap.set('{fullAdmitPayImmediatelyPaymentAmount}', (await getTotalAmountWithInterestAndFees(claim)).toString());
valuesMap.set('{TELL_US_IT_IS_SETTLED}', DATE_PAID_URL.replace(':id', claimId));
valuesMap.set('{DOWNLOAD_SETTLEMENT_AGREEMENT}', CASE_DOCUMENT_VIEW_URL.replace(':id', claimId).replace(':documentId', getSystemGeneratedCaseDocumentIdByType(claim.systemGeneratedCaseDocuments, DocumentType.SETTLEMENT_AGREEMENT)));
valuesMap.set('{MEDIATION}', MEDIATION_SERVICE_EXTERNAL);
Expand Down
Loading