FINERACT-2174: Allow for recognition of Interest on first day end of day - Introducing the flag

This commit is contained in:
Jose Alberto Hernandez
2025-02-05 19:52:44 +01:00
committed by Adam Saghy
parent 7d0474cc17
commit 3a5888ee4b
36 changed files with 366 additions and 414 deletions
@@ -244,7 +244,7 @@ public class LoansApiResource {
LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount,
LoanApiConstants.clientActiveLoanOptions, LoanApiConstants.datatables, LoanProductConstants.RATES_PARAM_NAME,
LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME, LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME,
LoanApiConstants.COLLECTION_PARAMNAME));
LoanApiConstants.COLLECTION_PARAMNAME, LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE));
private static final Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
private static final Set<String> GLIM_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(Arrays.asList("glimId", "groupId", "clientId",
@@ -1204,6 +1204,8 @@ final class LoansApiResourceSwagger {
@Schema(description = "List of GetLoansLoanIdLoanTermVariations")
public List<GetLoansLoanIdLoanTermVariations> loanTermVariations;
public StringEnumOptionData chargeOffBehaviour;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
}
@Schema(description = "GetLoansResponse")
@@ -1303,6 +1305,8 @@ final class LoansApiResourceSwagger {
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "10.00")
public BigDecimal fixedEmiAmount;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
public List<PostLoansRequestChargeData> charges;
@@ -1473,6 +1477,8 @@ final class LoansApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "false")
public Boolean interestRecognitionOnDisbursementDate;
static final class PutLoansLoanIdChanges {
@@ -266,6 +266,7 @@ public class LoanAccountData {
private Boolean enableDownPayment;
private BigDecimal disbursedAmountPercentageForDownPayment;
private Boolean enableAutoRepaymentForDownPayment;
private Boolean interestRecognitionOnDisbursementDate;
private EnumOptionData loanScheduleType;
private EnumOptionData loanScheduleProcessingType;
@@ -419,7 +420,8 @@ public class LoanAccountData {
.setIsEqualAmortization(product.isEqualAmortization())
.setFixedPrincipalPercentagePerInstallment(product.getFixedPrincipalPercentagePerInstallment())
.setDelinquent(CollectionData.template()).setDisallowExpectedDisbursements(product.getDisallowExpectedDisbursements())
.setLoanScheduleType(product.getLoanScheduleType()).setLoanScheduleProcessingType(product.getLoanScheduleProcessingType());
.setLoanScheduleType(product.getLoanScheduleType()).setLoanScheduleProcessingType(product.getLoanScheduleProcessingType())
.setInterestRecognitionOnDisbursementDate(product.isInterestRecognitionOnDisbursementDate());
}
/*
@@ -456,7 +458,8 @@ public class LoanAccountData {
LocalDate lastClosedBusinessDate, LocalDate overpaidOnDate, final boolean chargedOff, final boolean enableDownPayment,
final BigDecimal disbursedAmountPercentageForDownPayment, final boolean enableAutoRepaymentForDownPayment,
final boolean enableInstallmentLevelDelinquency, final EnumOptionData loanScheduleType,
final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour) {
final EnumOptionData loanScheduleProcessingType, final Integer fixedLength, final StringEnumOptionData chargeOffBehaviour,
final boolean isInterestRecognitionOnDisbursementDate) {
final CollectionData delinquent = CollectionData.template();
@@ -501,7 +504,8 @@ public class LoanAccountData {
.setEnableAutoRepaymentForDownPayment(enableAutoRepaymentForDownPayment)
.setEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency).setLoanScheduleType(loanScheduleType)
.setLoanScheduleProcessingType(loanScheduleProcessingType).setFixedLength(fixedLength)
.setChargeOffBehaviour(chargeOffBehaviour);
.setChargeOffBehaviour(chargeOffBehaviour)
.setInterestRecognitionOnDisbursementDate(isInterestRecognitionOnDisbursementDate);
}
/*
@@ -518,6 +518,12 @@ public class LoanScheduleAssembler {
fixedLength = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.FIXED_LENGTH, element);
}
Boolean interestRecognitionOnDisbursementDate = loanProduct.getLoanProductRelatedDetail().isInterestRecognitionOnDisbursementDate();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element)) {
interestRecognitionOnDisbursementDate = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element);
}
return LoanApplicationTerms.assembleFrom(applicationCurrency.toData(), loanTermFrequency, loanTermPeriodFrequencyType,
numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod,
interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod,
@@ -535,7 +541,7 @@ public class LoanScheduleAssembler {
loanScheduleType, loanScheduleProcessingType, fixedLength,
loanProduct.getLoanProductRelatedDetail().isEnableAccrualActivityPosting(),
loanProduct.getLoanProductRelatedDetail().getSupportedInterestRefundTypes(),
loanProduct.getLoanProductRelatedDetail().getChargeOffBehaviour());
loanProduct.getLoanProductRelatedDetail().getChargeOffBehaviour(), interestRecognitionOnDisbursementDate);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
@@ -1452,6 +1458,14 @@ public class LoanScheduleAssembler {
}
loanProductRelatedDetail.setDisbursedAmountPercentageForDownPayment(disbursedAmountPercentageDownPayment);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE,
loanProductRelatedDetail.isInterestRecognitionOnDisbursementDate())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE);
changes.put(LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, newValue);
loanProductRelatedDetail.updateInterestRecognitionOnDisbursementDate(newValue);
}
}
public Pair<Loan, Map<String, Object>> assembleLoanApproval(AppUser currentUser, JsonCommand command, Long loanId) {
@@ -169,7 +169,8 @@ public final class LoanApplicationValidator {
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME,
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH,
LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.ENABLE_DOWN_PAYMENT,
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT));
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
@@ -760,6 +761,14 @@ public final class LoanApplicationValidator {
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
fixedLengthValidations(element);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element)) {
if (!LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE);
throw new UnsupportedParameterException(unsupportedParameterList);
}
}
}
private void fixedLengthValidations(final JsonElement element) {
@@ -1442,6 +1451,14 @@ public final class LoanApplicationValidator {
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element)) {
if (!LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE);
throw new UnsupportedParameterException(unsupportedParameterList);
}
}
});
}
@@ -75,7 +75,7 @@ public final class LoanScheduleValidator {
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanProductConstants.FIXED_LENGTH, LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
LoanProductConstants.ENABLE_DOWN_PAYMENT, LoanProductConstants.DISBURSED_AMOUNT_PERCENTAGE_DOWN_PAYMENT,
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT));
LoanProductConstants.ENABLE_AUTO_REPAYMENT_DOWN_PAYMENT, LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE));
private final FromJsonHelper fromApiJsonHelper;
@@ -702,7 +702,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
+ " topup.topup_amount as topupAmount, l.last_closed_business_date as lastClosedBusinessDate,l.overpaidon_date as overpaidOnDate, "
+ " l.is_charged_off as isChargedOff, l.charge_off_reason_cv_id as chargeOffReasonId, codec.code_value as chargeOffReason, l.charged_off_on_date as chargedOffOnDate, l.enable_down_payment as enableDownPayment, l.disbursed_amount_percentage_for_down_payment as disbursedAmountPercentageForDownPayment, l.enable_auto_repayment_for_down_payment as enableAutoRepaymentForDownPayment,"
+ " cobu.username as chargedOffByUsername, cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname, l.loan_schedule_type as loanScheduleType, l.loan_schedule_processing_type as loanScheduleProcessingType, "
+ " l.charge_off_behaviour as chargeOffBehaviour " //
+ " l.charge_off_behaviour as chargeOffBehaviour, l.interest_recognition_on_disbursement_date as interestRecognitionOnDisbursementDate " //
+ " from m_loan l" //
+ " join m_product_loan lp on lp.id = l.product_id" //
+ " left join m_loan_recalculation_details lir on lir.loan_id = l.id join m_currency rc on rc."
@@ -1068,6 +1068,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LoanScheduleProcessingType loanScheduleProcessingType = LoanScheduleProcessingType.valueOf(loanScheduleProcessingTypeStr);
final Integer fixedLength = JdbcSupport.getInteger(rs, "fixedLength");
final LoanChargeOffBehaviour chargeOffBehaviour = LoanChargeOffBehaviour.valueOf(rs.getString("chargeOffBehaviour"));
final boolean interestRecognitionOnDisbursementDate = rs.getBoolean("interestRecognitionOnDisbursementDate");
return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName,
clientOfficeId, clientExternalId, groupData, loanType, loanProductId, loanProductName, loanProductDescription,
@@ -1086,7 +1087,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
fixedPrincipalPercentagePerInstallment, delinquencyRange, disallowExpectedDisbursements, isFraud,
lastClosedBusinessDate, overpaidOnDate, isChargedOff, enableDownPayment, disbursedAmountPercentageForDownPayment,
enableAutoRepaymentForDownPayment, enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(),
loanScheduleProcessingType.asEnumOptionData(), fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData());
loanScheduleProcessingType.asEnumOptionData(), fixedLength, chargeOffBehaviour.getValueAsStringEnumOptionData(),
interestRecognitionOnDisbursementDate);
}
}
@@ -185,7 +185,7 @@ public final class LoanProductDataValidator {
LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, LoanProductConstants.LOAN_SCHEDULE_TYPE,
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, LoanProductConstants.FIXED_LENGTH,
LoanProductConstants.ENABLE_ACCRUAL_ACTIVITY_POSTING, LoanProductConstants.SUPPORTED_INTEREST_REFUND_TYPES,
LoanProductConstants.CHARGE_OFF_BEHAVIOUR));
LoanProductConstants.CHARGE_OFF_BEHAVIOUR, LoanProductConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE));
private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { LoanProductConstants.amortizationTypeParamName,
LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -265,7 +265,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
+ "lpr.is_compounding_to_be_posted_as_transaction as isCompoundingToBePostedAsTransaction, "
+ "lpr.allow_compounding_on_eod as allowCompoundingOnEod, "
+ "lpr.disallow_interest_calc_on_past_due as disallowInterestCalculationOnPastDue, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, "
+ "lp.hold_guarantee_funds as holdGuaranteeFunds, lp.interest_recognition_on_disbursement_date as interestRecognitionOnDisbursementDate, "
+ "lp.principal_threshold_for_last_installment as principalThresholdForLastInstallment, "
+ "lp.fixed_principal_percentage_per_installment fixedPrincipalPercentagePerInstallment, "
+ "lp.sync_expected_with_disbursement_date as syncExpectedWithDisbursementDate, "
@@ -547,6 +547,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
}
final String chargeOffBehaviourStr = rs.getString("chargeOffBehaviour");
final LoanChargeOffBehaviour loanChargeOffBehaviour = LoanChargeOffBehaviour.valueOf(chargeOffBehaviourStr);
final boolean interestRecognitionOnDisbursementDate = rs.getBoolean("interestRecognitionOnDisbursementDate");
return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance,
numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod,
@@ -570,7 +571,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo
enableAutoRepaymentForDownPayment, advancedPaymentData, creditAllocationData, repaymentStartDateType,
enableInstallmentLevelDelinquency, loanScheduleType.asEnumOptionData(), loanScheduleProcessingType.asEnumOptionData(),
fixedLength, enableAccrualActivityPosting, supportedInterestRefundTypes,
loanChargeOffBehaviour.getValueAsStringEnumOptionData());
loanChargeOffBehaviour.getValueAsStringEnumOptionData(), interestRecognitionOnDisbursementDate);
}
}
@@ -98,7 +98,7 @@ public class DefaultScheduledDateGeneratorTest {
Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null, EMPTY_LIST, BigDecimal.valueOf(36_000L), null,
DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null, null, ZERO, null, NONE, null, ZERO,
EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false, null, false, DISBURSEMENT_DATE,
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null);
submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null, false);
// when
List<? extends LoanScheduleModelPeriod> result = underTest.generateRepaymentPeriods(mathContext, expectedDisbursementDate,
@@ -169,8 +169,8 @@ public class DefaultScheduledDateGeneratorTest {
dueRepaymentPeriodDate, null, null, null, null, null, Money.of(fromApplicationCurrency(dollarCurrency), ZERO), false, null,
EMPTY_LIST, BigDecimal.valueOf(36_000L), null, DaysInMonthType.ACTUAL, DaysInYearType.ACTUAL, false, null, null, null, null,
null, ZERO, null, NONE, null, ZERO, EMPTY_LIST, true, 0, false, holidayDetailDTO, false, false, false, null, false, false,
null, false, DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null,
null);
null, false, DISBURSEMENT_DATE, submittedOnDate, CUMULATIVE, LoanScheduleProcessingType.HORIZONTAL, null, false, null, null,
false);
}
private HolidayDetailDTO createHolidayDTO() {