FINERACT-2232: add income capitalization principal handling and repayment schedule calculations on loans
This commit is contained in:
+2
-2
@@ -43,8 +43,8 @@ public class AcmeNoteWritePlatformService implements NoteWritePlatformService, I
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLoanNote(Long loanId, String note) {
|
||||
throw new UnsupportedOperationException("createLoanNote() is not yet implemented.");
|
||||
public void createLoanTransactionNote(Long loanTransactionId, String note) {
|
||||
throw new UnsupportedOperationException("createLoanTransactionNote() is not yet implemented.");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+6
@@ -310,6 +310,12 @@ public final class DateUtils {
|
||||
return isBeforeBusinessDate(localDate);
|
||||
}
|
||||
|
||||
public static boolean isDateInRangeFromInclusiveToExclusive(final LocalDate fromInclusive, final LocalDate upToNotInclusive,
|
||||
final LocalDate target) {
|
||||
return (DateUtils.isEqual(target, fromInclusive) || DateUtils.isAfter(target, fromInclusive))
|
||||
&& DateUtils.isBefore(target, upToNotInclusive);
|
||||
}
|
||||
|
||||
public static int compare(LocalDate first, LocalDate second) {
|
||||
return compare(first, second, true);
|
||||
}
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ public interface NoteWritePlatformService {
|
||||
|
||||
CommandProcessingResult createNote(JsonCommand command);
|
||||
|
||||
void createLoanNote(Long loanId, String note);
|
||||
void createLoanTransactionNote(Long loanTransactionId, String note);
|
||||
|
||||
CommandProcessingResult updateNote(JsonCommand command);
|
||||
|
||||
|
||||
+7
-7
@@ -148,7 +148,7 @@ public class EventCheckHelper {
|
||||
.extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getId()).isEqualTo(body.getStatus().getId())//
|
||||
.extractingData(loanAccountDataV1 -> loanAccountDataV1.getStatus().getCode()).isEqualTo(body.getStatus().getCode())//
|
||||
.extractingData(LoanAccountDataV1::getClientId).isEqualTo(Long.valueOf(body.getClientId()))//
|
||||
.extractingBigDecimal(LoanAccountDataV1::getApprovedPrincipal).isEqualTo(BigDecimal.valueOf(body.getApprovedPrincipal()))//
|
||||
.extractingBigDecimal(LoanAccountDataV1::getApprovedPrincipal).isEqualTo(body.getApprovedPrincipal())//
|
||||
.extractingData(loanAccountDataV1 -> loanAccountDataV1.getTimeline().getApprovedOnDate())//
|
||||
.isEqualTo(FORMATTER_EVENTS.format(body.getTimeline().getApprovedOnDate()))//
|
||||
.extractingData(loanAccountDataV1 -> loanAccountDataV1.getSummary().getCurrency().getCode())
|
||||
@@ -198,7 +198,7 @@ public class EventCheckHelper {
|
||||
Long clientIdActual = loanAccountDataV1.getClientId();
|
||||
Long clientIdExpected = body.getClientId();
|
||||
BigDecimal principalDisbursedActual = loanAccountDataV1.getSummary().getPrincipalDisbursed();
|
||||
Double principalDisbursedExpectedDouble = body.getSummary().getPrincipalDisbursed();
|
||||
Double principalDisbursedExpectedDouble = body.getSummary().getPrincipalDisbursed().doubleValue();
|
||||
BigDecimal principalDisbursedExpected = BigDecimal.valueOf(principalDisbursedExpectedDouble);
|
||||
String actualDisbursementDateActual = loanAccountDataV1.getTimeline().getActualDisbursementDate();
|
||||
String actualDisbursementDateExpected = FORMATTER_EVENTS.format(body.getTimeline().getActualDisbursementDate());
|
||||
@@ -210,7 +210,7 @@ public class EventCheckHelper {
|
||||
.getTotalUnpaidPayableNotDueInterest();
|
||||
BigDecimal totalUnpaidPayableNotDueInterestExpected = body.getSummary().getTotalUnpaidPayableNotDueInterest();
|
||||
BigDecimal totalInterestPaymentWaiverActual = loanAccountDataV1.getSummary().getTotalInterestPaymentWaiver();
|
||||
Double totalInterestPaymentWaiverExpectedDouble = body.getSummary().getTotalInterestPaymentWaiver();
|
||||
Double totalInterestPaymentWaiverExpectedDouble = body.getSummary().getTotalInterestPaymentWaiver().doubleValue();
|
||||
BigDecimal totalInterestPaymentWaiverExpected = new BigDecimal(totalInterestPaymentWaiverExpectedDouble,
|
||||
MathContext.DECIMAL64);
|
||||
BigDecimal delinquentInterestActual = loanAccountDataV1.getDelinquent().getDelinquentInterest();
|
||||
@@ -296,7 +296,7 @@ public class EventCheckHelper {
|
||||
eventAssertion.assertEvent(LoanDisbursalTransactionEvent.class, disbursementTransaction.getId())//
|
||||
.extractingData(LoanTransactionDataV1::getLoanId).isEqualTo(body.getId())//
|
||||
.extractingData(LoanTransactionDataV1::getDate).isEqualTo(FORMATTER_EVENTS.format(disbursementTransaction.getDate()))//
|
||||
.extractingBigDecimal(LoanTransactionDataV1::getAmount).isEqualTo(BigDecimal.valueOf(disbursementTransaction.getAmount()));//
|
||||
.extractingBigDecimal(LoanTransactionDataV1::getAmount).isEqualTo(disbursementTransaction.getAmount());//
|
||||
}
|
||||
|
||||
public EventAssertion.EventAssertionBuilder<LoanTransactionDataV1> transactionEventCheck(
|
||||
@@ -325,7 +325,7 @@ public class EventCheckHelper {
|
||||
EventAssertion.EventAssertionBuilder<LoanTransactionDataV1> eventBuilder = eventAssertion.assertEvent(eventClass, transactionId);
|
||||
eventBuilder.extractingData(LoanTransactionDataV1::getLoanId).isEqualTo(loanDetailsResponse.body().getId())//
|
||||
.extractingData(LoanTransactionDataV1::getDate).isEqualTo(FORMATTER_EVENTS.format(transactionFound.getDate()))//
|
||||
.extractingBigDecimal(LoanTransactionDataV1::getAmount).isEqualTo(BigDecimal.valueOf(transactionFound.getAmount()))//
|
||||
.extractingBigDecimal(LoanTransactionDataV1::getAmount).isEqualTo(transactionFound.getAmount())//
|
||||
.extractingData(LoanTransactionDataV1::getExternalOwnerId).isEqualTo(externalOwnerId);//
|
||||
return eventBuilder;
|
||||
}
|
||||
@@ -420,8 +420,8 @@ public class EventCheckHelper {
|
||||
.getGlobalConfiguration("outstanding-interest-calculation-strategy-for-external-asset-transfer");
|
||||
if ("PAYABLE_OUTSTANDING_INTEREST".equals(outstandingInterestStrategy.getStringValue())) {
|
||||
Response<GetLoansLoanIdResponse> loanDetails = loansApi.retrieveLoan(loanId, false, "all", null, null).execute();
|
||||
totalOutstandingBalanceAmountExpected = BigDecimal.valueOf(loanDetails.body().getSummary().getTotalOutstanding());
|
||||
outstandingInterestPortionExpected = BigDecimal.valueOf(loanDetails.body().getSummary().getInterestOutstanding());
|
||||
totalOutstandingBalanceAmountExpected = loanDetails.body().getSummary().getTotalOutstanding();
|
||||
outstandingInterestPortionExpected = loanDetails.body().getSummary().getInterestOutstanding();
|
||||
}
|
||||
|
||||
String ownerExternalIdExpected = filtered.getStatus().getValue().equals("BUYBACK") ? null : filtered.getOwner().getExternalId();
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ public class LoanChargeAdjustmentStepDef extends AbstractStepDef {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT);
|
||||
String dateActual = formatter.format(date);
|
||||
|
||||
Double amountActual = transactions.get(i).getAmount();
|
||||
Double amountActual = transactions.get(i).getAmount().doubleValue();
|
||||
|
||||
if (dateActual.equals(transactionDate) && amountActual.equals(transactionAmount)) {
|
||||
transactionMetConditions = transactions.get(i);
|
||||
|
||||
+2
-2
@@ -654,7 +654,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef {
|
||||
Long loanLevelDelinquencyRangeIdExpected = delinquencyRange.getId();
|
||||
String loanLevelDelinquencyRangeExpected = delinquencyRange.getClassification();
|
||||
String loanLevelDelinquentDateExpected = FORMATTER.format(delinquent.getDelinquentDate());
|
||||
BigDecimal loanLevelTotalAmountExpected = BigDecimal.valueOf(delinquent.getDelinquentAmount());
|
||||
BigDecimal loanLevelTotalAmountExpected = delinquent.getDelinquentAmount();
|
||||
|
||||
assertThat(loanLevelDelinquencyRangeId)//
|
||||
.as(ErrorMessageHelper.wrongValueInLoanDelinquencyRangeChangeBusinessEvent4(loanLevelDelinquencyRangeId,
|
||||
@@ -715,7 +715,7 @@ public class LoanDelinquencyStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetails);
|
||||
|
||||
Double expectedLastRepaymentAmount1 = Double.valueOf(expectedLastRepaymentAmount);
|
||||
Double actualLastRepaymentAmount = loanDetails.body().getDelinquent().getLastRepaymentAmount();
|
||||
Double actualLastRepaymentAmount = loanDetails.body().getDelinquent().getLastRepaymentAmount().doubleValue();
|
||||
String actualLastRepaymentDate = FORMATTER.format(loanDetails.body().getDelinquent().getLastRepaymentDate());
|
||||
|
||||
assertThat(actualLastRepaymentAmount)//
|
||||
|
||||
+2
-2
@@ -550,7 +550,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef {
|
||||
BigDecimal lastExpectedAmount = new BigDecimal(totalAmount).setScale(0, RoundingMode.HALF_DOWN);
|
||||
|
||||
for (int i = 1; i < periods.size(); i++) {
|
||||
BigDecimal actualAmount = new BigDecimal(periods.get(i).getPrincipalOriginalDue()).setScale(0, RoundingMode.HALF_DOWN);
|
||||
BigDecimal actualAmount = periods.get(i).getPrincipalOriginalDue().setScale(0, RoundingMode.HALF_DOWN);
|
||||
|
||||
if (i == periods.size() - 1) {
|
||||
assertThat(actualAmount.compareTo(lastExpectedAmount))
|
||||
@@ -613,7 +613,7 @@ public class LoanRepaymentStepDef extends AbstractStepDef {
|
||||
eventAssertionBuilder
|
||||
.extractingBigDecimal(
|
||||
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getAmount())
|
||||
.isEqualTo(BigDecimal.valueOf(targetTransaction.getAmount()));
|
||||
.isEqualTo(targetTransaction.getAmount());
|
||||
eventAssertionBuilder
|
||||
.extractingData(
|
||||
loanTransactionAdjustmentDataV1 -> loanTransactionAdjustmentDataV1.getTransactionToAdjust().getManuallyReversed())
|
||||
|
||||
+79
-74
@@ -1121,9 +1121,9 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
|
||||
GetLoansLoanIdDelinquencySummary delinquent = loanDetailsResponse.body().getDelinquent();
|
||||
String lastPaymentAmountActual = String.valueOf(delinquent.getLastPaymentAmount());
|
||||
String lastPaymentAmountActual = new Utils.DoubleFormatter(delinquent.getLastPaymentAmount().doubleValue()).format();
|
||||
String lastPaymentDateActual = FORMATTER.format(delinquent.getLastPaymentDate());
|
||||
String lastRepaymentAmountActual = String.valueOf(delinquent.getLastRepaymentAmount());
|
||||
String lastRepaymentAmountActual = new Utils.DoubleFormatter(delinquent.getLastRepaymentAmount().doubleValue()).format();
|
||||
String lastRepaymentDateActual = FORMATTER.format(delinquent.getLastRepaymentDate());
|
||||
|
||||
assertThat(lastPaymentAmountActual)
|
||||
@@ -1832,7 +1832,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
|
||||
|
||||
Double totalOutstandingActual = loanDetailsResponse.body().getSummary().getTotalOutstanding();
|
||||
Double totalOutstandingActual = loanDetailsResponse.body().getSummary().getTotalOutstanding().doubleValue();
|
||||
assertThat(totalOutstandingActual)
|
||||
.as(ErrorMessageHelper.wrongAmountInTotalOutstanding(totalOutstandingActual, totalOutstandingExpected))
|
||||
.isEqualTo(totalOutstandingExpected);
|
||||
@@ -1847,8 +1847,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
|
||||
|
||||
Double totalOverpaidActual = loanDetailsResponse.body().getTotalOverpaid();
|
||||
Double totalOutstandingActual = loanDetailsResponse.body().getSummary().getTotalOutstanding();
|
||||
Double totalOverpaidActual = loanDetailsResponse.body().getTotalOverpaid().doubleValue();
|
||||
Double totalOutstandingActual = loanDetailsResponse.body().getSummary().getTotalOutstanding().doubleValue();
|
||||
double totalOutstandingExpected = 0.0;
|
||||
assertThat(totalOutstandingActual)
|
||||
.as(ErrorMessageHelper.wrongAmountInTotalOutstanding(totalOutstandingActual, totalOutstandingExpected))
|
||||
@@ -1867,7 +1867,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
|
||||
|
||||
Double totalOverdueActual = loanDetailsResponse.body().getSummary().getTotalOverdue();
|
||||
Double totalOverdueActual = loanDetailsResponse.body().getSummary().getTotalOverdue().doubleValue();
|
||||
assertThat(totalOverdueActual).as(ErrorMessageHelper.wrongAmountInTotalOverdue(totalOverdueActual, totalOverdueExpected))
|
||||
.isEqualTo(totalOverdueExpected);
|
||||
}
|
||||
@@ -1881,7 +1881,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
|
||||
|
||||
Double lastPaymentAmountActual = loanDetailsResponse.body().getDelinquent().getLastPaymentAmount();
|
||||
Double lastPaymentAmountActual = loanDetailsResponse.body().getDelinquent().getLastPaymentAmount().doubleValue();
|
||||
assertThat(lastPaymentAmountActual)
|
||||
.as(ErrorMessageHelper.wrongLastPaymentAmount(lastPaymentAmountActual, lastPaymentAmountExpected))
|
||||
.isEqualTo(lastPaymentAmountExpected);
|
||||
@@ -2173,12 +2173,13 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
actualValues.add(t.getDueDate() == null ? null : FORMATTER.format(t.getDueDate()));
|
||||
actualValues.add(t.getChargeCalculationType().getValue() == null ? null : t.getChargeCalculationType().getValue());
|
||||
|
||||
actualValues.add(t.getAmount() == null ? null : new Utils.DoubleFormatter(t.getAmount()).format());
|
||||
actualValues.add(t.getAmount() == null ? null : new Utils.DoubleFormatter(t.getAmount().doubleValue()).format());
|
||||
|
||||
actualValues.add(t.getAmountPaid() == null ? null : new Utils.DoubleFormatter(t.getAmountPaid()).format());
|
||||
actualValues.add(t.getAmountWaived() == null ? null : new Utils.DoubleFormatter(t.getAmountWaived()).format());
|
||||
actualValues.add(t.getAmountPaid() == null ? null : new Utils.DoubleFormatter(t.getAmountPaid().doubleValue()).format());
|
||||
actualValues.add(t.getAmountWaived() == null ? null : new Utils.DoubleFormatter(t.getAmountWaived().doubleValue()).format());
|
||||
|
||||
actualValues.add(t.getAmountOutstanding() == null ? null : new Utils.DoubleFormatter(t.getAmountOutstanding()).format());
|
||||
actualValues.add(
|
||||
t.getAmountOutstanding() == null ? null : new Utils.DoubleFormatter(t.getAmountOutstanding().doubleValue()).format());
|
||||
return actualValues;
|
||||
}).collect(Collectors.toList());
|
||||
}
|
||||
@@ -2681,9 +2682,9 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
|
||||
|
||||
GetLoansLoanIdDelinquencySummary delinquent = loanDetailsResponse.body().getDelinquent();
|
||||
String lastPaymentAmountActual = String.valueOf(delinquent.getLastPaymentAmount());
|
||||
String lastPaymentAmountActual = new Utils.DoubleFormatter(delinquent.getLastPaymentAmount().doubleValue()).format();
|
||||
String lastPaymentDateActual = FORMATTER.format(delinquent.getLastPaymentDate());
|
||||
String lastRepaymentAmountActual = String.valueOf(delinquent.getLastRepaymentAmount());
|
||||
String lastRepaymentAmountActual = new Utils.DoubleFormatter(delinquent.getLastRepaymentAmount().doubleValue()).format();
|
||||
String lastRepaymentDateActual = FORMATTER.format(delinquent.getLastRepaymentDate());
|
||||
|
||||
assertThat(lastPaymentAmountActual)
|
||||
@@ -2923,7 +2924,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
ErrorHelper.checkSuccessfulApiCall(loanDetails);
|
||||
|
||||
Double expectedAmountParsed = Double.parseDouble(expectedAmount);
|
||||
Double totalRepaymentTransaction = loanDetails.body().getSummary().getTotalRepaymentTransaction();
|
||||
Double totalRepaymentTransaction = loanDetails.body().getSummary().getTotalRepaymentTransaction().doubleValue();
|
||||
|
||||
assertThat(totalRepaymentTransaction)
|
||||
.as(ErrorMessageHelper.wrongAmountInTotalRepaymentTransaction(totalRepaymentTransaction, expectedAmountParsed))
|
||||
@@ -3310,8 +3311,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
final Double disbursementPrincipalAmount = Double.valueOf(loanData.get(19));
|
||||
|
||||
List<PostLoansDisbursementData> disbursementDetail = new ArrayList<>();
|
||||
disbursementDetail.add(
|
||||
new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDate).principal(disbursementPrincipalAmount));
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDate)
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmount)));
|
||||
|
||||
createFullyCustomizedLoanWithChargesExpectsTrancheDisbursementDetails(loanData, disbursementDetail);
|
||||
}
|
||||
@@ -3325,9 +3326,9 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
|
||||
List<PostLoansDisbursementData> disbursementDetail = new ArrayList<>();
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDateFirstDisbursal)
|
||||
.principal(disbursementPrincipalAmountFirstDisbursal));
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmountFirstDisbursal)));
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDateSecondDisbursal)
|
||||
.principal(disbursementPrincipalAmountSecondDisbursal));
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmountSecondDisbursal)));
|
||||
|
||||
createFullyCustomizedLoanWithChargesExpectsTrancheDisbursementDetails(loanData, disbursementDetail);
|
||||
}
|
||||
@@ -3418,8 +3419,8 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
final Double disbursementPrincipalAmount = Double.valueOf(loanData.get(17));
|
||||
|
||||
List<PostLoansDisbursementData> disbursementDetail = new ArrayList<>();
|
||||
disbursementDetail.add(
|
||||
new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDate).principal(disbursementPrincipalAmount));
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDate)
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmount)));
|
||||
|
||||
createFullyCustomizedLoanExpectsTrancheDisbursementDetails(loanData, disbursementDetail);
|
||||
}
|
||||
@@ -3433,9 +3434,9 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
|
||||
List<PostLoansDisbursementData> disbursementDetail = new ArrayList<>();
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDateFirstDisbursal)
|
||||
.principal(disbursementPrincipalAmountFirstDisbursal));
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmountFirstDisbursal)));
|
||||
disbursementDetail.add(new PostLoansDisbursementData().expectedDisbursementDate(expectedDisbursementDateSecondDisbursal)
|
||||
.principal(disbursementPrincipalAmountSecondDisbursal));
|
||||
.principal(BigDecimal.valueOf(disbursementPrincipalAmountSecondDisbursal)));
|
||||
|
||||
createFullyCustomizedLoanExpectsTrancheDisbursementDetails(loanData, disbursementDetail);
|
||||
}
|
||||
@@ -3698,16 +3699,20 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
switch (headerName) {
|
||||
case "Transaction date" -> actualValues.add(t.getDate() == null ? null : FORMATTER.format(t.getDate()));
|
||||
case "Transaction Type" -> actualValues.add(t.getType().getValue() == null ? null : t.getType().getValue());
|
||||
case "Amount" -> actualValues.add(t.getAmount() == null ? null : String.valueOf(t.getAmount()));
|
||||
case "Principal" -> actualValues.add(t.getPrincipalPortion() == null ? null : String.valueOf(t.getPrincipalPortion()));
|
||||
case "Interest" -> actualValues.add(t.getInterestPortion() == null ? null : String.valueOf(t.getInterestPortion()));
|
||||
case "Fees" -> actualValues.add(t.getFeeChargesPortion() == null ? null : String.valueOf(t.getFeeChargesPortion()));
|
||||
case "Penalties" ->
|
||||
actualValues.add(t.getPenaltyChargesPortion() == null ? null : String.valueOf(t.getPenaltyChargesPortion()));
|
||||
case "Loan Balance" ->
|
||||
actualValues.add(t.getOutstandingLoanBalance() == null ? null : String.valueOf(t.getOutstandingLoanBalance()));
|
||||
case "Overpayment" ->
|
||||
actualValues.add(t.getOverpaymentPortion() == null ? null : String.valueOf(t.getOverpaymentPortion()));
|
||||
case "Amount" ->
|
||||
actualValues.add(t.getAmount() == null ? null : new Utils.DoubleFormatter(t.getAmount().doubleValue()).format());
|
||||
case "Principal" -> actualValues.add(
|
||||
t.getPrincipalPortion() == null ? null : new Utils.DoubleFormatter(t.getPrincipalPortion().doubleValue()).format());
|
||||
case "Interest" -> actualValues.add(
|
||||
t.getInterestPortion() == null ? null : new Utils.DoubleFormatter(t.getInterestPortion().doubleValue()).format());
|
||||
case "Fees" -> actualValues.add(t.getFeeChargesPortion() == null ? null
|
||||
: new Utils.DoubleFormatter(t.getFeeChargesPortion().doubleValue()).format());
|
||||
case "Penalties" -> actualValues.add(t.getPenaltyChargesPortion() == null ? null
|
||||
: new Utils.DoubleFormatter(t.getPenaltyChargesPortion().doubleValue()).format());
|
||||
case "Loan Balance" -> actualValues.add(t.getOutstandingLoanBalance() == null ? null
|
||||
: new Utils.DoubleFormatter(t.getOutstandingLoanBalance().doubleValue()).format());
|
||||
case "Overpayment" -> actualValues.add(t.getOverpaymentPortion() == null ? null
|
||||
: new Utils.DoubleFormatter(t.getOverpaymentPortion().doubleValue()).format());
|
||||
case "Reverted" -> actualValues.add(t.getManuallyReversed() == null ? null : String.valueOf(t.getManuallyReversed()));
|
||||
case "Replayed" -> {
|
||||
boolean hasReplayed = t.getTransactionRelations().stream().anyMatch(e -> "REPLAYED".equals(e.getRelationType()));
|
||||
@@ -3748,27 +3753,27 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
case "Paid date" -> actualValues.add(repaymentPeriod.getObligationsMetOnDate() == null ? null
|
||||
: FORMATTER.format(repaymentPeriod.getObligationsMetOnDate()));
|
||||
case "Balance of loan" -> actualValues.add(repaymentPeriod.getPrincipalLoanBalanceOutstanding() == null ? null
|
||||
: String.valueOf(repaymentPeriod.getPrincipalLoanBalanceOutstanding()));
|
||||
case "Principal due" ->
|
||||
actualValues.add(repaymentPeriod.getPrincipalDue() == null ? null : String.valueOf(repaymentPeriod.getPrincipalDue()));
|
||||
case "Interest" ->
|
||||
actualValues.add(repaymentPeriod.getInterestDue() == null ? null : String.valueOf(repaymentPeriod.getInterestDue()));
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getPrincipalLoanBalanceOutstanding().doubleValue()).format());
|
||||
case "Principal due" -> actualValues.add(repaymentPeriod.getPrincipalDue() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getPrincipalDue().doubleValue()).format());
|
||||
case "Interest" -> actualValues.add(repaymentPeriod.getInterestDue() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getInterestDue().doubleValue()).format());
|
||||
case "Fees" -> actualValues.add(repaymentPeriod.getFeeChargesDue() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getFeeChargesDue()).format());
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getFeeChargesDue().doubleValue()).format());
|
||||
case "Penalties" -> actualValues.add(repaymentPeriod.getPenaltyChargesDue() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getPenaltyChargesDue()).format());
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getPenaltyChargesDue().doubleValue()).format());
|
||||
case "Due" -> actualValues.add(repaymentPeriod.getTotalDueForPeriod() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalDueForPeriod()).format());
|
||||
case "Paid" -> actualValues.add(
|
||||
repaymentPeriod.getTotalPaidForPeriod() == null ? null : String.valueOf(repaymentPeriod.getTotalPaidForPeriod()));
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalDueForPeriod().doubleValue()).format());
|
||||
case "Paid" -> actualValues.add(repaymentPeriod.getTotalPaidForPeriod() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidForPeriod().doubleValue()).format());
|
||||
case "In advance" -> actualValues.add(repaymentPeriod.getTotalPaidInAdvanceForPeriod() == null ? null
|
||||
: String.valueOf(repaymentPeriod.getTotalPaidInAdvanceForPeriod()));
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidInAdvanceForPeriod().doubleValue()).format());
|
||||
case "Late" -> actualValues.add(repaymentPeriod.getTotalPaidLateForPeriod() == null ? null
|
||||
: String.valueOf(repaymentPeriod.getTotalPaidLateForPeriod()));
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalPaidLateForPeriod().doubleValue()).format());
|
||||
case "Waived" -> actualValues.add(repaymentPeriod.getTotalWaivedForPeriod() == null ? null
|
||||
: String.valueOf(repaymentPeriod.getTotalWaivedForPeriod()));
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalWaivedForPeriod().doubleValue()).format());
|
||||
case "Outstanding" -> actualValues.add(repaymentPeriod.getTotalOutstandingForPeriod() == null ? null
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalOutstandingForPeriod()).format());
|
||||
: new Utils.DoubleFormatter(repaymentPeriod.getTotalOutstandingForPeriod().doubleValue()).format());
|
||||
default -> throw new IllegalStateException(String.format("Header name %s cannot be found", headerName));
|
||||
}
|
||||
}
|
||||
@@ -3784,7 +3789,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = repaymentSchedule.getPeriods();
|
||||
for (GetLoansLoanIdRepaymentPeriod period : periods) {
|
||||
if (null != period.getTotalPaidForPeriod()) {
|
||||
paidActual += period.getTotalPaidForPeriod();
|
||||
paidActual += period.getTotalPaidForPeriod().doubleValue();
|
||||
}
|
||||
}
|
||||
BigDecimal paidActualBd = new BigDecimal(paidActual).setScale(2, RoundingMode.HALF_DOWN);
|
||||
@@ -3793,45 +3798,45 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
String headerName = header.get(i);
|
||||
String expectedValue = expectedAmounts.get(i);
|
||||
switch (headerName) {
|
||||
case "Principal due" -> assertThat(repaymentSchedule.getTotalPrincipalExpected())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePrincipal(repaymentSchedule.getTotalPrincipalExpected(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Principal due" -> assertThat(repaymentSchedule.getTotalPrincipalExpected().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePrincipal(
|
||||
repaymentSchedule.getTotalPrincipalExpected().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Interest" -> assertThat(repaymentSchedule.getTotalInterestCharged())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInterest(repaymentSchedule.getTotalInterestCharged(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Interest" -> assertThat(repaymentSchedule.getTotalInterestCharged().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInterest(
|
||||
repaymentSchedule.getTotalInterestCharged().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Fees" -> assertThat(repaymentSchedule.getTotalFeeChargesCharged())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleFees(repaymentSchedule.getTotalFeeChargesCharged(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Fees" -> assertThat(repaymentSchedule.getTotalFeeChargesCharged().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleFees(
|
||||
repaymentSchedule.getTotalFeeChargesCharged().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Penalties" -> assertThat(repaymentSchedule.getTotalPenaltyChargesCharged())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePenalties(repaymentSchedule.getTotalPenaltyChargesCharged(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Penalties" -> assertThat(repaymentSchedule.getTotalPenaltyChargesCharged().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePenalties(
|
||||
repaymentSchedule.getTotalPenaltyChargesCharged().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Due" -> assertThat(repaymentSchedule.getTotalRepaymentExpected())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleDue(repaymentSchedule.getTotalRepaymentExpected(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Due" -> assertThat(repaymentSchedule.getTotalRepaymentExpected().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleDue(
|
||||
repaymentSchedule.getTotalRepaymentExpected().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Paid" -> assertThat(paidActualBd.doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentSchedulePaid(paidActualBd.doubleValue(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "In advance" -> assertThat(repaymentSchedule.getTotalPaidInAdvance())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInAdvance(repaymentSchedule.getTotalPaidInAdvance(),
|
||||
case "In advance" -> assertThat(repaymentSchedule.getTotalPaidInAdvance().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleInAdvance(
|
||||
repaymentSchedule.getTotalPaidInAdvance().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Late" -> assertThat(repaymentSchedule.getTotalPaidLate().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleLate(repaymentSchedule.getTotalPaidLate().doubleValue(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Late" -> assertThat(repaymentSchedule.getTotalPaidLate())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleLate(repaymentSchedule.getTotalPaidLate(),
|
||||
case "Waived" -> assertThat(repaymentSchedule.getTotalWaived().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleWaived(repaymentSchedule.getTotalWaived().doubleValue(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Waived" -> assertThat(repaymentSchedule.getTotalWaived())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleWaived(repaymentSchedule.getTotalWaived(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
case "Outstanding" -> assertThat(repaymentSchedule.getTotalOutstanding())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleOutstanding(repaymentSchedule.getTotalOutstanding(),
|
||||
Double.valueOf(expectedValue)))//
|
||||
case "Outstanding" -> assertThat(repaymentSchedule.getTotalOutstanding().doubleValue())//
|
||||
.as(ErrorMessageHelper.wrongAmountInRepaymentScheduleOutstanding(
|
||||
repaymentSchedule.getTotalOutstanding().doubleValue(), Double.valueOf(expectedValue)))//
|
||||
.isEqualTo(Double.valueOf(expectedValue));//
|
||||
}
|
||||
}
|
||||
@@ -3853,7 +3858,7 @@ public class LoanStepDef extends AbstractStepDef {
|
||||
case "Applicable From" -> actualValues.add(emiVariation.getTermVariationApplicableFrom() == null ? null
|
||||
: FORMATTER.format(emiVariation.getTermVariationApplicableFrom()));
|
||||
case "Decimal Value" ->
|
||||
actualValues.add(emiVariation.getDecimalValue() == null ? null : String.valueOf(emiVariation.getDecimalValue()));
|
||||
actualValues.add(emiVariation.getDecimalValue() == null ? null : new Utils.DoubleFormatter(emiVariation.getDecimalValue().doubleValue()).format());
|
||||
case "Date Value" ->
|
||||
actualValues.add(emiVariation.getDateValue() == null ? null : FORMATTER.format(emiVariation.getDateValue()));
|
||||
case "Is Specific To Installment" -> actualValues.add(String.valueOf(emiVariation.getIsSpecificToInstallment()));
|
||||
|
||||
+39
-39
@@ -188,12 +188,12 @@ Feature: Capitalized Income
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | | 100.0 |
|
||||
When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "2 January 2024" with 900.17 EUR transaction amount and system-generated Idempotency key
|
||||
When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "2 January 2024" with 1000.17 EUR transaction amount and system-generated Idempotency key
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 02 January 2024 | Repayment | 900.17 | 900.0 | 0.17 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 02 January 2024 | Repayment | 1000.17| 1000.0 | 0.17 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 02 January 2024 | Accrual | 0.17 | 0.0 | 0.17 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 02 January 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "02 January 2024" which has the following Journal entries:
|
||||
@@ -222,12 +222,12 @@ Feature: Capitalized Income
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | 100.0 | |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | | 100.0 |
|
||||
When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "2 January 2024" with 1000 EUR transaction amount and system-generated Idempotency key
|
||||
When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on "2 January 2024" with 1100 EUR transaction amount and system-generated Idempotency key
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 02 January 2024 | Repayment | 1000.0 | 900.0 | 0.17 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 02 January 2024 | Repayment | 1100.0 | 1000.0 | 0.17 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 02 January 2024 | Accrual | 0.17 | 0.0 | 0.17 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 02 January 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "02 January 2024" which has the following Journal entries:
|
||||
@@ -262,7 +262,7 @@ Feature: Capitalized Income
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 02 January 2024 | Close (as written-off) | 905.25 | 900.0 | 5.25 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 02 January 2024 | Close (as written-off) | 1005.81| 1000.0 | 5.81 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 02 January 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "02 January 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
@@ -555,16 +555,16 @@ Feature: Capitalized Income
|
||||
And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024"
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 900.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.0 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.0 | |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.58 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 1000.0| |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.58 | |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.58 | 0.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 1004.58| 1000.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
@@ -597,16 +597,16 @@ Feature: Capitalized Income
|
||||
And Admin does charge-off the loan on "03 February 2024"
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 900.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.0 |
|
||||
| EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | 900.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.0 | |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.58 |
|
||||
| EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | 1000.0| |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.58 | |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.58 | 0.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 1004.58| 1000.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
@@ -639,16 +639,16 @@ Feature: Capitalized Income
|
||||
And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024"
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 900.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.0 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.0 | |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.58 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 1000.0| |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.58 | |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.58 | 0.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 1004.58| 1000.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
@@ -681,16 +681,16 @@ Feature: Capitalized Income
|
||||
And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024"
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 900.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.0 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.0 | |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.58 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 1000.0| |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.58 | |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.58 | 0.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 1004.58| 1000.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Capitalized Income Amortization | 100.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
@@ -700,20 +700,20 @@ Feature: Capitalized Income
|
||||
Then Admin does a charge-off undo the loan
|
||||
Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| ASSET | 112601 | Loans Receivable | | 900.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.0 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.0 | |
|
||||
| ASSET | 112601 | Loans Receivable | 900.0 | |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | 4.0 | |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 900.0 |
|
||||
| INCOME | 404001 | Interest Income Charge Off | | 4.0 |
|
||||
| ASSET | 112601 | Loans Receivable | | 1000.0 |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | | 4.58 |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 1000.0| |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 4.58 | |
|
||||
| ASSET | 112601 | Loans Receivable | 1000.0| |
|
||||
| ASSET | 112603 | Interest/Fee Receivable | 4.58 | |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 1000.0 |
|
||||
| INCOME | 404001 | Interest Income Charge Off | | 4.58 |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 900.0 | 0.0 | 0.0 | 0.0 | 0.0 | 900.0 | false |
|
||||
| 02 January 2024 | Capitalized Income | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false |
|
||||
| 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | true |
|
||||
| 03 February 2024 | Accrual | 4.58 | 0.0 | 4.58 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 03 February 2024 | Charge-off | 1004.58| 1000.0 | 4.58 | 0.0 | 0.0 | 0.0 | true |
|
||||
Then Reversed loan capitalized income amortization transaction has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 |
|
||||
|
||||
+2
-3
@@ -30,7 +30,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public final class DisbursementData implements Comparable<DisbursementData> {
|
||||
public final class DisbursementData implements LoanPrincipalRelatedDataHolder, Comparable<DisbursementData> {
|
||||
|
||||
private final Long id;
|
||||
private final LocalDate expectedDisbursementDate;
|
||||
@@ -102,8 +102,7 @@ public final class DisbursementData implements Comparable<DisbursementData> {
|
||||
|
||||
private boolean occursOnDayFromAndIncludingAndUpTo(final LocalDate fromInclusive, final LocalDate upToNotInclusive,
|
||||
final LocalDate target) {
|
||||
return (DateUtils.isEqual(target, fromInclusive) || DateUtils.isAfter(target, fromInclusive))
|
||||
&& DateUtils.isBefore(target, upToNotInclusive);
|
||||
return DateUtils.isDateInRangeFromInclusiveToExclusive(fromInclusive, upToNotInclusive, target);
|
||||
}
|
||||
|
||||
public BigDecimal getWaivedChargeAmount() {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.data;
|
||||
|
||||
public interface LoanPrincipalRelatedDataHolder {}
|
||||
+2
@@ -34,6 +34,8 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
public class LoanSummaryData {
|
||||
|
||||
private final CurrencyData currency;
|
||||
private final BigDecimal totalPrincipal;
|
||||
private final BigDecimal totalPrincipalFromCapitalizedIncome;
|
||||
private final BigDecimal principalDisbursed;
|
||||
private final BigDecimal principalAdjustments;
|
||||
private final BigDecimal principalPaid;
|
||||
|
||||
+17
-4
@@ -38,6 +38,12 @@ import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
public class LoanSummary {
|
||||
|
||||
// derived totals fields
|
||||
@Column(name = "total_principal_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPrincipal;
|
||||
|
||||
@Column(name = "capitalized_income_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPrincipalFromCapitalizedIncome;
|
||||
|
||||
@Column(name = "principal_disbursed_derived", scale = 6, precision = 19)
|
||||
private BigDecimal totalPrincipalDisbursed;
|
||||
|
||||
@@ -166,6 +172,8 @@ public class LoanSummary {
|
||||
this.totalPenaltyChargesWaived = BigDecimal.ZERO;
|
||||
this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO;
|
||||
this.totalPrincipalAdjustments = BigDecimal.ZERO;
|
||||
this.totalPrincipal = BigDecimal.ZERO;
|
||||
this.totalPrincipalFromCapitalizedIncome = BigDecimal.ZERO;
|
||||
this.totalPrincipalDisbursed = BigDecimal.ZERO;
|
||||
this.totalPrincipalOutstanding = BigDecimal.ZERO;
|
||||
this.totalPrincipalRepaid = BigDecimal.ZERO;
|
||||
@@ -176,17 +184,22 @@ public class LoanSummary {
|
||||
}
|
||||
|
||||
public void updateSummary(final MonetaryCurrency currency, final Money principal,
|
||||
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges) {
|
||||
final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges,
|
||||
List<LoanTransaction> capitalizedIncomeTransactions) {
|
||||
final BigDecimal principalFromCapitalizedIncome = capitalizedIncomeTransactions.stream().filter(LoanTransaction::isNotReversed)
|
||||
.map(LoanTransaction::getPrincipalPortion).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
this.totalPrincipalDisbursed = principal.getAmount();
|
||||
this.totalPrincipalFromCapitalizedIncome = principalFromCapitalizedIncome;
|
||||
this.totalPrincipal = principal.plus(principalFromCapitalizedIncome).getAmount();
|
||||
this.totalPrincipalAdjustments = calculateTotalPrincipalAdjusted(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalFeeAdjustments = calculateTotalFeeAdjusted(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPenaltyAdjustments = calculateTotalPenaltyAdjusted(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPrincipalRepaid = calculateTotalPrincipalRepaid(repaymentScheduleInstallments, currency).getAmount();
|
||||
this.totalPrincipalWrittenOff = calculateTotalPrincipalWrittenOff(repaymentScheduleInstallments, currency).getAmount();
|
||||
|
||||
this.totalPrincipalOutstanding = principal.plus(this.totalPrincipalAdjustments).minus(this.totalPrincipalRepaid)
|
||||
.minus(this.totalPrincipalWrittenOff).getAmount();
|
||||
this.totalPrincipalOutstanding = principal.plus(principalFromCapitalizedIncome).plus(this.totalPrincipalAdjustments)
|
||||
.minus(this.totalPrincipalRepaid).minus(this.totalPrincipalWrittenOff).getAmount();
|
||||
|
||||
final Money totalInterestCharged = calculateTotalInterestCharged(repaymentScheduleInstallments, currency);
|
||||
this.totalInterestCharged = totalInterestCharged.getAmount();
|
||||
@@ -225,7 +238,7 @@ public class LoanSummary {
|
||||
this.totalPenaltyChargesOutstanding = totalPenaltyChargesCharged.minus(this.totalPenaltyChargesRepaid)
|
||||
.minus(this.totalPenaltyChargesWaived).minus(this.totalPenaltyChargesWrittenOff).getAmount();
|
||||
|
||||
final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipalDisbursed).plus(this.totalInterestCharged)
|
||||
final Money totalExpectedRepayment = Money.of(currency, this.totalPrincipal).plus(this.totalInterestCharged)
|
||||
.plus(this.totalFeeChargesCharged).plus(this.totalPenaltyChargesCharged);
|
||||
this.totalExpectedRepayment = totalExpectedRepayment.getAmount();
|
||||
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanPrincipalRelatedDataHolder;
|
||||
|
||||
/**
|
||||
* Immutable data object representing a subset of loan transaction data.
|
||||
*/
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
public class LoanTransactionRepaymentPeriodData implements LoanPrincipalRelatedDataHolder, Serializable {
|
||||
|
||||
private final Long transactionId;
|
||||
private final Long loanId;
|
||||
private final LocalDate date;
|
||||
private final boolean reversed;
|
||||
private final BigDecimal amount;
|
||||
private final BigDecimal unrecognizedAmount;
|
||||
private final BigDecimal feeChargesPortion;
|
||||
|
||||
public LoanTransactionRepaymentPeriodData(Long transactionId, Long loanId, LocalDate date, boolean reversed, BigDecimal amount,
|
||||
BigDecimal unrecognizedAmount, BigDecimal feeChargesPortion) {
|
||||
this.transactionId = transactionId;
|
||||
this.loanId = loanId;
|
||||
this.date = date;
|
||||
this.reversed = reversed;
|
||||
this.amount = amount;
|
||||
this.unrecognizedAmount = unrecognizedAmount;
|
||||
this.feeChargesPortion = feeChargesPortion;
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -104,7 +104,10 @@ public final class LoanRefundValidator {
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan() && adjustedTransaction == null) {
|
||||
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
final BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
final BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted);
|
||||
final BigDecimal totalPrincipalFromCapitalizedIncome = loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome).stream()
|
||||
.filter(LoanTransaction::isNotReversed).map(LoanTransaction::getPrincipalPortion)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
final BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted).add(totalPrincipalFromCapitalizedIncome);
|
||||
if (totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) < 0) {
|
||||
final String errorMessage = "The transaction amount cannot exceed threshold.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "amount.exceeds.threshold", errorMessage);
|
||||
|
||||
+2
-1
@@ -97,7 +97,8 @@ public class LoanBalanceService {
|
||||
loan.setTotalRecovered(recoveredAmount.getAmountDefaultedToNullIfZero());
|
||||
|
||||
final Money principal = loan.getLoanRepaymentScheduleDetail().getPrincipal();
|
||||
loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges());
|
||||
List<LoanTransaction> capitalizedIncomeTransactions = loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome);
|
||||
loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(), capitalizedIncomeTransactions);
|
||||
loan.updateLoanOutstandingBalances();
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -173,7 +173,10 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
||||
final BigDecimal totalDisbursed = loan.getDisbursedAmount();
|
||||
final BigDecimal totalPrincipalAdjusted = loan.getSummary().getTotalPrincipalAdjustments();
|
||||
final BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted);
|
||||
final BigDecimal totalPrincipalFromCapitalizedIncome = loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome).stream()
|
||||
.filter(LoanTransaction::isNotReversed).map(LoanTransaction::getPrincipalPortion)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
final BigDecimal totalPrincipalCredited = totalDisbursed.add(totalPrincipalAdjusted).add(totalPrincipalFromCapitalizedIncome);
|
||||
if (totalPrincipalCredited.compareTo(loan.getSummary().getTotalPrincipalRepaid()) < 0
|
||||
&& loan.repaymentScheduleDetail().getPrincipal().minus(totalDisbursed).isGreaterThanZero()) {
|
||||
final String errorMessage = "The transaction amount cannot exceed threshold.";
|
||||
|
||||
+3
-1
@@ -37,6 +37,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
|
||||
@@ -50,7 +51,8 @@ public interface LoanReadPlatformService {
|
||||
LoanAccountData fetchRepaymentScheduleData(LoanAccountData accountData);
|
||||
|
||||
LoanScheduleData retrieveRepaymentSchedule(Long loanId, RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData,
|
||||
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType);
|
||||
Collection<DisbursementData> disbursementData, Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData,
|
||||
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType);
|
||||
|
||||
Collection<LoanTransactionData> retrieveLoanTransactions(Long loanId);
|
||||
|
||||
|
||||
+71
-18
@@ -344,6 +344,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
|
||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
|
||||
case CAPITALIZED_INCOME -> handleCapitalizedIncome(loanTransaction, ctx);
|
||||
// TODO: Cover rest of the transaction types
|
||||
default -> log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||
}
|
||||
@@ -1199,18 +1200,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
emiCalculator.addDisbursement(model, transactionDate, amortizableAmount);
|
||||
|
||||
disbursementTransaction.resetDerivedComponents();
|
||||
if (amortizableAmount.isGreaterThanZero()) {
|
||||
model.repaymentPeriods().forEach(rm -> {
|
||||
LoanRepaymentScheduleInstallment installment = installments.stream().filter(
|
||||
ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment() && !ri.getDueDate().isBefore(transactionDate))
|
||||
.findFirst().orElse(null);
|
||||
if (installment != null) {
|
||||
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
|
||||
installment.updateInterestCharged(rm.getDueInterest().getAmount());
|
||||
installment.updateObligationsMet(currency, transactionDate);
|
||||
}
|
||||
});
|
||||
}
|
||||
recalculateRepaymentPeriodsWithEMICalculation(amortizableAmount, model, installments, transactionDate, currency);
|
||||
allocateOverpayment(disbursementTransaction, transactionCtx);
|
||||
}
|
||||
|
||||
@@ -1222,7 +1212,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
|
||||
i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
|
||||
.toList();
|
||||
int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = disbursementTransaction.getLoan().getLoanRepaymentScheduleDetail();
|
||||
Integer installmentAmountInMultiplesOf = disbursementTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||
Money downPaymentAmount = Money.zero(currency);
|
||||
@@ -1240,7 +1229,73 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
|
||||
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
||||
|
||||
recalculateRepaymentInstallmentsWithoutEMICalculation(disbursementTransaction, amortizableAmount, candidateRepaymentInstallments,
|
||||
currency, installmentAmountInMultiplesOf);
|
||||
allocateOverpayment(disbursementTransaction, transactionCtx);
|
||||
}
|
||||
|
||||
private void handleCapitalizedIncome(LoanTransaction capitalizedIncomeTransaction, TransactionCtx transactionCtx) {
|
||||
if (capitalizedIncomeTransaction.getLoan().isInterestBearing()) {
|
||||
handleCapitalizedIncomeWithEMICalculator(capitalizedIncomeTransaction, transactionCtx);
|
||||
} else {
|
||||
handleCapitalizedIncomeWithoutEMICalculator(capitalizedIncomeTransaction, transactionCtx);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCapitalizedIncomeWithEMICalculator(LoanTransaction capitalizedIncomeTransaction, TransactionCtx transactionCtx) {
|
||||
ProgressiveLoanInterestScheduleModel model;
|
||||
if (!(transactionCtx instanceof ProgressiveTransactionCtx)
|
||||
|| (model = ((ProgressiveTransactionCtx) transactionCtx).getModel()) == null) {
|
||||
throw new IllegalStateException("TransactionCtx has no model");
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
|
||||
LocalDate transactionDate = capitalizedIncomeTransaction.getTransactionDate();
|
||||
MonetaryCurrency currency = transactionCtx.getCurrency();
|
||||
|
||||
Money amortizableAmount = capitalizedIncomeTransaction.getAmount(currency);
|
||||
emiCalculator.addCapitalizedIncome(model, transactionDate, amortizableAmount);
|
||||
|
||||
recalculateRepaymentPeriodsWithEMICalculation(amortizableAmount, model, installments, transactionDate, currency);
|
||||
allocateOverpayment(capitalizedIncomeTransaction, transactionCtx);
|
||||
}
|
||||
|
||||
private void handleCapitalizedIncomeWithoutEMICalculator(LoanTransaction capitalizedIncomeTransaction, TransactionCtx transactionCtx) {
|
||||
MonetaryCurrency currency = transactionCtx.getCurrency();
|
||||
List<LoanRepaymentScheduleInstallment> installments = transactionCtx.getInstallments();
|
||||
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
|
||||
i -> i.getDueDate().isAfter(capitalizedIncomeTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
|
||||
.toList();
|
||||
Integer installmentAmountInMultiplesOf = capitalizedIncomeTransaction.getLoan().getLoanProduct()
|
||||
.getInstallmentAmountInMultiplesOf();
|
||||
|
||||
Money amortizableAmount = capitalizedIncomeTransaction.getAmount(currency);
|
||||
|
||||
recalculateRepaymentInstallmentsWithoutEMICalculation(capitalizedIncomeTransaction, amortizableAmount,
|
||||
candidateRepaymentInstallments, currency, installmentAmountInMultiplesOf);
|
||||
allocateOverpayment(capitalizedIncomeTransaction, transactionCtx);
|
||||
}
|
||||
|
||||
private void recalculateRepaymentPeriodsWithEMICalculation(Money amortizableAmount, ProgressiveLoanInterestScheduleModel model,
|
||||
List<LoanRepaymentScheduleInstallment> installments, LocalDate transactionDate, MonetaryCurrency currency) {
|
||||
if (amortizableAmount.isGreaterThanZero()) {
|
||||
model.repaymentPeriods().forEach(rm -> {
|
||||
LoanRepaymentScheduleInstallment installment = installments.stream().filter(
|
||||
ri -> ri.getDueDate().equals(rm.getDueDate()) && !ri.isDownPayment() && !ri.getDueDate().isBefore(transactionDate))
|
||||
.findFirst().orElse(null);
|
||||
if (installment != null) {
|
||||
installment.updatePrincipal(rm.getDuePrincipal().getAmount());
|
||||
installment.updateInterestCharged(rm.getDueInterest().getAmount());
|
||||
installment.updateObligationsMet(currency, transactionDate);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateRepaymentInstallmentsWithoutEMICalculation(LoanTransaction loanTransaction, Money amortizableAmount,
|
||||
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments, MonetaryCurrency currency,
|
||||
Integer installmentAmountInMultiplesOf) {
|
||||
if (amortizableAmount.isGreaterThanZero()) {
|
||||
int noCandidateRepaymentInstallments = candidateRepaymentInstallments.size();
|
||||
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, MoneyHelper.getMathContext());
|
||||
MoneyHolder moneyHolder = new MoneyHolder(amortizableAmount);
|
||||
|
||||
@@ -1252,14 +1307,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
i.updatePrincipal(newPrincipal.getAmount());
|
||||
moneyHolder.setMoneyObject(moneyHolder.getMoneyObject().minus(newPrincipal).plus(previousPrincipal));
|
||||
i.updateObligationsMet(currency, disbursementTransaction.getTransactionDate());
|
||||
i.updateObligationsMet(currency, loanTransaction.getTransactionDate());
|
||||
});
|
||||
// Hence the rounding, we might need to amend the last installment amount
|
||||
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
|
||||
.addToPrincipal(disbursementTransaction.getTransactionDate(), moneyHolder.getMoneyObject());
|
||||
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1).addToPrincipal(loanTransaction.getTransactionDate(),
|
||||
moneyHolder.getMoneyObject());
|
||||
}
|
||||
|
||||
allocateOverpayment(disbursementTransaction, transactionCtx);
|
||||
}
|
||||
|
||||
private void allocateOverpayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) {
|
||||
|
||||
+7
@@ -20,13 +20,20 @@ package org.apache.fineract.portfolio.loanaccount.repository;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
|
||||
public interface LoanCapitalizedIncomeBalanceRepository
|
||||
extends JpaRepository<LoanCapitalizedIncomeBalance, Long>, JpaSpecificationExecutor<LoanCapitalizedIncomeBalance> {
|
||||
|
||||
String FIND_BALANCE_REPAYMENT_SCHEDULE_DATA = "SELECT new org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData(lcib.loanTransaction.id, lcib.loan.id, lcib.loanTransaction.dateOf, lcib.loanTransaction.reversed, lcib.amount, lcib.unrecognizedAmount, lcib.loanTransaction.feeChargesPortion) FROM LoanCapitalizedIncomeBalance lcib ";
|
||||
|
||||
List<LoanCapitalizedIncomeBalance> findAllByLoanId(Long loanId);
|
||||
|
||||
LoanCapitalizedIncomeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId);
|
||||
|
||||
@Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lcib.loan.id = :loanId")
|
||||
List<LoanTransactionRepaymentPeriodData> findRepaymentPeriodDataByLoanId(Long loanId);
|
||||
}
|
||||
|
||||
+13
-2
@@ -30,6 +30,7 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
@@ -54,6 +55,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
private final NoteWritePlatformService noteWritePlatformService;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository;
|
||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -74,9 +76,10 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
final Money capitalizedIncomeAmount = calculateCapitalizedIncomeAmount(loan, transactionAmount);
|
||||
final LoanTransaction capitalizedIncomeTransaction = LoanTransaction.capitalizedIncome(loan, capitalizedIncomeAmount, paymentDetail,
|
||||
transactionDate, txnExternalId);
|
||||
// TODO: Transaction reverse-replay + loan repayment schedule update (follow-up story)
|
||||
// Update loan with capitalized income
|
||||
loan.addLoanTransaction(capitalizedIncomeTransaction);
|
||||
// Recalculate loan transactions
|
||||
recalculateLoanTransactions(loan, transactionDate, capitalizedIncomeTransaction);
|
||||
// Create capitalized income balances
|
||||
createCapitalizedIncomeBalance(capitalizedIncomeTransaction);
|
||||
// Save and flush (PK is set)
|
||||
@@ -88,7 +91,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
// Create a note if provided
|
||||
final String noteText = command.stringValueOfParameterNamed("note");
|
||||
if (noteText != null && !noteText.isEmpty()) {
|
||||
noteWritePlatformService.createLoanNote(loanId, noteText);
|
||||
noteWritePlatformService.createLoanTransactionNote(capitalizedIncomeTransaction.getId(), noteText);
|
||||
}
|
||||
// Post journal entries
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
@@ -99,6 +102,14 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc
|
||||
.build();
|
||||
}
|
||||
|
||||
private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction capitalizedIncomeTransaction) {
|
||||
if (loan.isInterestBearingAndInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) {
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
} else {
|
||||
reprocessLoanTransactionsService.processLatestTransaction(capitalizedIncomeTransaction, loan);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetBalance(final Long loanId) {
|
||||
capitalizedIncomeBalanceRepository
|
||||
|
||||
+4
-2
@@ -30,6 +30,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
||||
import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -45,10 +46,11 @@ public class ProgressiveLoanAccountConfiguration {
|
||||
LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository,
|
||||
PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster journalEntryPoster,
|
||||
NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory,
|
||||
LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository) {
|
||||
LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository,
|
||||
ReprocessLoanTransactionsService reprocessLoanTransactionsService) {
|
||||
return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
|
||||
paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory,
|
||||
capitalizedIncomeBalanceRepository);
|
||||
capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+6
@@ -65,6 +65,12 @@ public interface EMICalculator {
|
||||
*/
|
||||
void addDisbursement(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate disbursementDueDate, Money disbursedAmount);
|
||||
|
||||
/**
|
||||
* Applies the capitalized income transaction on the interest model. This method recalculates the EMI amounts from
|
||||
* the action date.
|
||||
*/
|
||||
void addCapitalizedIncome(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDueDate, Money transactionAmount);
|
||||
|
||||
/**
|
||||
* Applies the interest rate change on the interest model. This method recalculates the EMI amounts from the action
|
||||
* date.
|
||||
|
||||
+29
-6
@@ -119,7 +119,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
private void addDisbursement(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
scheduleModel
|
||||
.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), operation.getAmount(),
|
||||
scheduleModel.zero())
|
||||
scheduleModel.zero(), scheduleModel.zero())
|
||||
.ifPresent((repaymentPeriod) -> calculateEMIValueAndRateFactors(
|
||||
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, operation.getSubmittedOnDate()), scheduleModel,
|
||||
operation));
|
||||
@@ -140,6 +140,24 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
return changedRepaymentPeriod.getDueDate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add capitalized income to Interest Period
|
||||
*/
|
||||
@Override
|
||||
public void addCapitalizedIncome(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate transactionDueDate,
|
||||
final Money transactionAmount) {
|
||||
addCapitalizedIncome(scheduleModel, EmiChangeOperation.capitalizedIncome(transactionDueDate, transactionAmount));
|
||||
}
|
||||
|
||||
private void addCapitalizedIncome(final ProgressiveLoanInterestScheduleModel scheduleModel, final EmiChangeOperation operation) {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(operation.getSubmittedOnDate(), scheduleModel.zero(),
|
||||
scheduleModel.zero(), operation.getAmount()).ifPresent((repaymentPeriod) -> {
|
||||
calculateEMIValueAndRateFactors(
|
||||
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, operation.getSubmittedOnDate()), scheduleModel,
|
||||
operation);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeInterestRate(final ProgressiveLoanInterestScheduleModel scheduleModel, final LocalDate newInterestSubmittedOnDate,
|
||||
final BigDecimal newInterestRate) {
|
||||
@@ -151,7 +169,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
scheduleModel.addInterestRate(interestRateChangeEffectiveDate, operation.getInterestRate());
|
||||
scheduleModel
|
||||
.changeOutstandingBalanceAndUpdateInterestPeriods(interestRateChangeEffectiveDate, scheduleModel.zero(),
|
||||
scheduleModel.zero())
|
||||
scheduleModel.zero(), scheduleModel.zero())
|
||||
.ifPresent(repaymentPeriod -> calculateEMIValueAndRateFactors(
|
||||
getEffectiveRepaymentDueDate(scheduleModel, repaymentPeriod, interestRateChangeEffectiveDate), scheduleModel,
|
||||
operation));
|
||||
@@ -160,8 +178,8 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
@Override
|
||||
public void addBalanceCorrection(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate balanceCorrectionDate,
|
||||
Money balanceCorrectionAmount) {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.zero(), balanceCorrectionAmount)
|
||||
.ifPresent(repaymentPeriod -> {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(balanceCorrectionDate, scheduleModel.zero(), balanceCorrectionAmount,
|
||||
scheduleModel.zero()).ifPresent(repaymentPeriod -> {
|
||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
calculateOutstandingBalance(scheduleModel);
|
||||
calculateLastUnpaidRepaymentPeriodEMI(scheduleModel);
|
||||
@@ -237,7 +255,7 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
|
||||
private void addCredit(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate transactionDate, Money creditedPrincipalAmount,
|
||||
Money creditedInterestAmount) {
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), creditedPrincipalAmount)
|
||||
scheduleModel.changeOutstandingBalanceAndUpdateInterestPeriods(transactionDate, scheduleModel.zero(), creditedPrincipalAmount, scheduleModel.zero())
|
||||
.ifPresent(repaymentPeriod -> {
|
||||
addCreditedAmountsToInterestPeriod(scheduleModel, transactionDate, creditedPrincipalAmount, creditedInterestAmount);
|
||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
||||
@@ -442,8 +460,11 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getDisbursementAmount))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)) // 100
|
||||
.plus(scheduleModel.getTotalCreditedPrincipal(), mc); //
|
||||
Money totalCapitalizedIncome = scheduleModel.repaymentPeriods().stream()
|
||||
.flatMap(rp -> rp.getInterestPeriods().stream().map(InterestPeriod::getCapitalizedIncomePrincipal))
|
||||
.reduce(scheduleModel.zero(), (m1, m2) -> m1.plus(m2, mc)); // 100
|
||||
|
||||
Money diff = totalDisbursedAmount.plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
Money diff = totalDisbursedAmount.plus(totalCapitalizedIncome, mc).plus(totalDueInterest, mc).minus(totalEMI, mc);
|
||||
Optional<RepaymentPeriod> findLastUnpaidRepaymentPeriod = scheduleModel.repaymentPeriods().stream().filter(rp -> !rp.isFullyPaid())
|
||||
.reduce((first, second) -> second);
|
||||
findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> {
|
||||
@@ -809,7 +830,9 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
||||
return;
|
||||
}
|
||||
final ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.copyWithoutPaidAmounts();
|
||||
|
||||
addDisbursement(scheduleModelCopy, operation.withZeroAmount());
|
||||
addCapitalizedIncome(scheduleModelCopy, operation.withZeroAmount());
|
||||
|
||||
final LocalDate firstDueDate = repaymentPeriods.get(0).getDueDate();
|
||||
scheduleModel.copyPeriodsFrom(firstDueDate, scheduleModelCopy.repaymentPeriods(), (newRepaymentPeriod, actualRepaymentPeriod) -> {
|
||||
|
||||
+7
-2
@@ -31,7 +31,8 @@ public class EmiChangeOperation {
|
||||
|
||||
public enum Action { //
|
||||
DISBURSEMENT, //
|
||||
INTEREST_RATE_CHANGE //
|
||||
INTEREST_RATE_CHANGE, //
|
||||
CAPITALIZED_INCOME //
|
||||
}
|
||||
|
||||
private final Action action;
|
||||
@@ -48,8 +49,12 @@ public class EmiChangeOperation {
|
||||
return new EmiChangeOperation(EmiChangeOperation.Action.INTEREST_RATE_CHANGE, newInterestSubmittedOnDate, null, newInterestRate);
|
||||
}
|
||||
|
||||
public static EmiChangeOperation capitalizedIncome(final LocalDate transactionDueDate, final Money transactionAmount) {
|
||||
return new EmiChangeOperation(Action.CAPITALIZED_INCOME, transactionDueDate, transactionAmount, null);
|
||||
}
|
||||
|
||||
public EmiChangeOperation withZeroAmount() {
|
||||
if (action == Action.DISBURSEMENT) {
|
||||
if (action == Action.DISBURSEMENT || action == Action.CAPITALIZED_INCOME) {
|
||||
return new EmiChangeOperation(action, submittedOnDate, amount.zero(), null);
|
||||
}
|
||||
return null;
|
||||
|
||||
+15
-7
@@ -62,6 +62,8 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
private Money balanceCorrectionAmount;
|
||||
private Money outstandingLoanBalance;
|
||||
|
||||
private Money capitalizedIncomePrincipal;
|
||||
|
||||
@JsonExclude
|
||||
private final MathContext mc;
|
||||
private final boolean isPaused;
|
||||
@@ -70,38 +72,38 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
|
||||
interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getOutstandingLoanBalance(), mc, interestPeriod.isPaused());
|
||||
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getCapitalizedIncomePrincipal(), mc, interestPeriod.isPaused());
|
||||
}
|
||||
|
||||
public static InterestPeriod empty(@NotNull RepaymentPeriod repaymentPeriod, MathContext mc) {
|
||||
return new InterestPeriod(repaymentPeriod, null, null, null, null, null, null, null, null, null, mc, false);
|
||||
return new InterestPeriod(repaymentPeriod, null, null, null, null, null, null, null, null, null, null, mc, false);
|
||||
}
|
||||
|
||||
public static InterestPeriod copy(@NotNull RepaymentPeriod repaymentPeriod, @NotNull InterestPeriod interestPeriod) {
|
||||
return new InterestPeriod(repaymentPeriod, interestPeriod.getFromDate(), interestPeriod.getDueDate(),
|
||||
interestPeriod.getRateFactor(), interestPeriod.getRateFactorTillPeriodDueDate(), interestPeriod.getCreditedPrincipal(),
|
||||
interestPeriod.getCreditedInterest(), interestPeriod.getDisbursementAmount(), interestPeriod.getBalanceCorrectionAmount(),
|
||||
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getMc(), interestPeriod.isPaused());
|
||||
interestPeriod.getOutstandingLoanBalance(), interestPeriod.getCapitalizedIncomePrincipal(), interestPeriod.getMc(), interestPeriod.isPaused());
|
||||
}
|
||||
|
||||
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
||||
LocalDate dueDate) {
|
||||
final Money zero = repaymentPeriod.getEmi().zero();
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero,
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||
zero.getMc(), false);
|
||||
}
|
||||
|
||||
public static InterestPeriod withEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate, LocalDate dueDate,
|
||||
boolean isPaused) {
|
||||
final Money zero = repaymentPeriod.getEmi().zero();
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero,
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||
zero.getMc(), isPaused);
|
||||
}
|
||||
|
||||
public static InterestPeriod withPausedAndEmptyAmounts(@NotNull RepaymentPeriod repaymentPeriod, @NotNull LocalDate fromDate,
|
||||
LocalDate dueDate) {
|
||||
final Money zero = repaymentPeriod.getEmi().zero();
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero,
|
||||
return new InterestPeriod(repaymentPeriod, fromDate, dueDate, BigDecimal.ZERO, BigDecimal.ZERO, zero, zero, zero, zero, zero, zero,
|
||||
zero.getMc(), true);
|
||||
}
|
||||
|
||||
@@ -126,6 +128,10 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
this.creditedInterest = MathUtil.plus(this.creditedInterest, creditedInterest, mc);
|
||||
}
|
||||
|
||||
public void addCapitalizedIncomePrincipalAmount(final Money capitalizedIncomePrincipal) {
|
||||
this.capitalizedIncomePrincipal = MathUtil.plus(this.capitalizedIncomePrincipal, capitalizedIncomePrincipal, mc);
|
||||
}
|
||||
|
||||
public BigDecimal getCalculatedDueInterest() {
|
||||
if (isPaused) {
|
||||
return creditedInterest.getAmount();
|
||||
@@ -156,6 +162,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
InterestPeriod previousInterestPeriod = previousRepaymentPeriod.get().getLastInterestPeriod();
|
||||
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance()//
|
||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)//
|
||||
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), mc)//
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc)//
|
||||
.minus(previousRepaymentPeriod.get().getDuePrincipal(), mc)//
|
||||
.plus(previousRepaymentPeriod.get().getPaidPrincipal(), mc), mc);//
|
||||
@@ -165,6 +172,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
InterestPeriod previousInterestPeriod = getRepaymentPeriod().getInterestPeriods().get(index - 1);
|
||||
this.outstandingLoanBalance = MathUtil.negativeToZero(previousInterestPeriod.getOutstandingLoanBalance() //
|
||||
.plus(previousInterestPeriod.getBalanceCorrectionAmount(), mc) //
|
||||
.plus(previousInterestPeriod.getCapitalizedIncomePrincipal(), mc) //
|
||||
.plus(previousInterestPeriod.getDisbursementAmount(), mc)); //
|
||||
}
|
||||
}
|
||||
@@ -173,7 +181,7 @@ public class InterestPeriod implements Comparable<InterestPeriod> {
|
||||
* Include principal like amounts (all disbursement amount + credited principal)
|
||||
*/
|
||||
public Money getCreditedAmounts() {
|
||||
return getDisbursementAmount().plus(getCreditedPrincipal(), mc);
|
||||
return getDisbursementAmount().plus(getCreditedPrincipal(), mc).plus(getCapitalizedIncomePrincipal(), mc);
|
||||
}
|
||||
|
||||
public boolean isFirstInterestPeriod() {
|
||||
|
||||
+8
-5
@@ -175,9 +175,10 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
|
||||
public Optional<RepaymentPeriod> changeOutstandingBalanceAndUpdateInterestPeriods(final LocalDate balanceChangeDate,
|
||||
final Money disbursedAmount, final Money correctionAmount) {
|
||||
final Money disbursedAmount, final Money correctionAmount, final Money capitalizedIncomePrincipal) {
|
||||
return findRepaymentPeriodForBalanceChange(balanceChangeDate).stream()//
|
||||
.peek(updateInterestPeriodOnRepaymentPeriod(balanceChangeDate, disbursedAmount, correctionAmount))//
|
||||
.peek(updateInterestPeriodOnRepaymentPeriod(balanceChangeDate, disbursedAmount, correctionAmount,
|
||||
capitalizedIncomePrincipal))//
|
||||
.findFirst();//
|
||||
}
|
||||
|
||||
@@ -214,7 +215,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
|
||||
private Consumer<RepaymentPeriod> updateInterestPeriodOnRepaymentPeriod(final LocalDate balanceChangeDate, final Money disbursedAmount,
|
||||
final Money correctionAmount) {
|
||||
final Money correctionAmount, final Money capitalizedIncomePrincipal) {
|
||||
return repaymentPeriod -> {
|
||||
final boolean isChangeOnMaturityDate = isLastRepaymentPeriod(repaymentPeriod)
|
||||
&& balanceChangeDate.isEqual(repaymentPeriod.getDueDate());
|
||||
@@ -222,9 +223,10 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
isChangeOnMaturityDate);
|
||||
if (interestPeriodOptional.isPresent()) {
|
||||
interestPeriodOptional.get().addDisbursementAmount(disbursedAmount);
|
||||
interestPeriodOptional.get().addCapitalizedIncomePrincipalAmount(capitalizedIncomePrincipal);
|
||||
interestPeriodOptional.get().addBalanceCorrectionAmount(correctionAmount);
|
||||
} else {
|
||||
insertInterestPeriod(repaymentPeriod, balanceChangeDate, disbursedAmount, correctionAmount);
|
||||
insertInterestPeriod(repaymentPeriod, balanceChangeDate, disbursedAmount, correctionAmount, capitalizedIncomePrincipal);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -246,7 +248,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
}
|
||||
|
||||
void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate balanceChangeDate, final Money disbursedAmount,
|
||||
final Money correctionAmount) {
|
||||
final Money correctionAmount, Money capitalizedIncomePrincipal) {
|
||||
final InterestPeriod previousInterestPeriod = findPreviousInterestPeriod(repaymentPeriod, balanceChangeDate);
|
||||
final LocalDate originalDueDate = previousInterestPeriod.getDueDate();
|
||||
final LocalDate newDueDate = calculateNewDueDate(previousInterestPeriod, balanceChangeDate);
|
||||
@@ -254,6 +256,7 @@ public class ProgressiveLoanInterestScheduleModel {
|
||||
|
||||
previousInterestPeriod.setDueDate(newDueDate);
|
||||
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
|
||||
previousInterestPeriod.addCapitalizedIncomePrincipalAmount(capitalizedIncomePrincipal);
|
||||
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
|
||||
|
||||
final InterestPeriod interestPeriod = InterestPeriod.withEmptyAmounts(repaymentPeriod, newDueDate, originalDueDate, isPaused);
|
||||
|
||||
+16
-1
@@ -225,6 +225,17 @@ public final class RepaymentPeriod {
|
||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum of capitalized income principals
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Money getCapitalizedIncomePrincipal() {
|
||||
return MathUtil.negativeToZero(interestPeriods.stream() //
|
||||
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
||||
.reduce(getZero(mc), (value, previous) -> value.plus(previous, mc)), mc); //
|
||||
}
|
||||
|
||||
/**
|
||||
* Gives back due principal + credited principal or paid principal
|
||||
*
|
||||
@@ -278,6 +289,7 @@ public final class RepaymentPeriod {
|
||||
InterestPeriod lastInterestPeriod = getInterestPeriods().get(getInterestPeriods().size() - 1);
|
||||
Money calculatedOutStandingLoanBalance = lastInterestPeriod.getOutstandingLoanBalance() //
|
||||
.plus(lastInterestPeriod.getBalanceCorrectionAmount(), mc) //
|
||||
.plus(lastInterestPeriod.getCapitalizedIncomePrincipal(), mc) //
|
||||
.plus(lastInterestPeriod.getDisbursementAmount(), mc) //
|
||||
.minus(getDuePrincipal(), mc)//
|
||||
.plus(getPaidPrincipal(), mc);//
|
||||
@@ -305,7 +317,10 @@ public final class RepaymentPeriod {
|
||||
Money totalDisbursedAmount = getInterestPeriods().stream() //
|
||||
.map(InterestPeriod::getDisbursementAmount) //
|
||||
.reduce(getZero(mc), (m1, m2) -> m1.plus(m2, mc)); //
|
||||
return initialBalance.add(totalDisbursedAmount, mc);
|
||||
Money totalCapitalizedIncomeAmount = getInterestPeriods().stream() //
|
||||
.map(InterestPeriod::getCapitalizedIncomePrincipal) //
|
||||
.reduce(getZero(mc), (m1, m2) -> m1.plus(m2, mc)); //
|
||||
return initialBalance.add(totalDisbursedAmount, mc).add(totalCapitalizedIncomeAmount, mc);
|
||||
}
|
||||
|
||||
private Money getZero(MathContext mc) {
|
||||
|
||||
+1
@@ -464,6 +464,7 @@ public class LoanImportHandler implements ImportHandler {
|
||||
JsonObject loanRepaymentJsonob = gsonBuilder.create().toJsonTree(loanRepayments.get(rowIndex)).getAsJsonObject();
|
||||
loanRepaymentJsonob.remove("manuallyReversed");
|
||||
loanRepaymentJsonob.remove("numberOfRepayments");
|
||||
loanRepaymentJsonob.remove("reversed");
|
||||
String payload = loanRepaymentJsonob.toString();
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder() //
|
||||
.loanRepaymentTransaction(result.getLoanId()) //
|
||||
|
||||
+9
-2
@@ -139,6 +139,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeTyp
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.NotSupportedLoanTemplateTypeException;
|
||||
@@ -150,6 +151,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||
@@ -297,6 +299,7 @@ public class LoansApiResource {
|
||||
private final ClientReadPlatformService clientReadPlatformService;
|
||||
private final LoanTermVariationsRepository loanTermVariationsRepository;
|
||||
private final LoanSummaryProviderDelegate loanSummaryProviderDelegate;
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
|
||||
/*
|
||||
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
|
||||
@@ -498,11 +501,13 @@ public class LoansApiResource {
|
||||
loanBasicDetails.getPageItems().forEach(i -> {
|
||||
if (i.getSummary() != null) {
|
||||
Collection<DisbursementData> disbursementData = this.loanReadPlatformService.retrieveLoanDisbursementDetails(i.getId());
|
||||
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = this.loanCapitalizedIncomeBalanceRepository
|
||||
.findRepaymentPeriodDataByLoanId(i.getId());
|
||||
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
|
||||
i.getTimeline().getExpectedDisbursementDate(), i.getTimeline().getActualDisbursementDate(), i.getCurrency(),
|
||||
i.getPrincipal(), i.getInArrearsTolerance(), i.getFeeChargesAtDisbursementCharged());
|
||||
final LoanScheduleData repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(i.getId(),
|
||||
repaymentScheduleRelatedData, disbursementData, i.isInterestRecalculationEnabled(),
|
||||
repaymentScheduleRelatedData, disbursementData, capitalizedIncomeData, i.isInterestRecalculationEnabled(),
|
||||
LoanScheduleType.fromEnumOptionData(i.getLoanScheduleType()));
|
||||
LoanSummaryDataProvider loanSummaryDataProvider = loanSummaryProviderDelegate
|
||||
.resolveLoanSummaryDataProvider(i.getTransactionProcessingStrategyCode());
|
||||
@@ -981,13 +986,15 @@ public class LoansApiResource {
|
||||
|
||||
if (associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
|
||||
mandatoryResponseParameters.add(DataTableApiConstant.repaymentScheduleAssociateParamName);
|
||||
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = this.loanCapitalizedIncomeBalanceRepository
|
||||
.findRepaymentPeriodDataByLoanId(resolvedLoanId);
|
||||
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
|
||||
loanBasicDetails.getTimeline().getExpectedDisbursementDate(),
|
||||
loanBasicDetails.getTimeline().getActualDisbursementDate(), loanBasicDetails.getCurrency(),
|
||||
loanBasicDetails.getPrincipal(), loanBasicDetails.getInArrearsTolerance(),
|
||||
loanBasicDetails.getFeeChargesAtDisbursementCharged());
|
||||
repaymentSchedule = this.loanReadPlatformService.retrieveRepaymentSchedule(resolvedLoanId, repaymentScheduleRelatedData,
|
||||
disbursementData, loanBasicDetails.isInterestRecalculationEnabled(),
|
||||
disbursementData, capitalizedIncomeData, loanBasicDetails.isInterestRecalculationEnabled(),
|
||||
LoanScheduleType.fromEnumOptionData(loanBasicDetails.getLoanScheduleType()));
|
||||
|
||||
if (associationParameters.contains(DataTableApiConstant.futureScheduleAssociateParamName)
|
||||
|
||||
+153
-149
@@ -42,9 +42,9 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2012, 4, 3]")
|
||||
public LocalDate approvalDate;
|
||||
@Schema(example = "200.000000")
|
||||
public Double approvalAmount;
|
||||
public BigDecimal approvalAmount;
|
||||
@Schema(example = "200.000000")
|
||||
public Double netDisbursalAmount;
|
||||
public BigDecimal netDisbursalAmount;
|
||||
|
||||
public GetLoanCurrency currency;
|
||||
}
|
||||
@@ -289,29 +289,29 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "30")
|
||||
public Long loanTermInDays;
|
||||
@Schema(example = "200.000000")
|
||||
public Double totalPrincipalDisbursed;
|
||||
public BigDecimal totalPrincipalDisbursed;
|
||||
@Schema(example = "200.00")
|
||||
public Double totalPrincipalExpected;
|
||||
public BigDecimal totalPrincipalExpected;
|
||||
@Schema(example = "200.00")
|
||||
public Double totalPrincipalPaid;
|
||||
public BigDecimal totalPrincipalPaid;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalInterestCharged;
|
||||
public BigDecimal totalInterestCharged;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalFeeChargesCharged;
|
||||
public BigDecimal totalFeeChargesCharged;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalPenaltyChargesCharged;
|
||||
public BigDecimal totalPenaltyChargesCharged;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalWaived;
|
||||
public BigDecimal totalWaived;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalWrittenOff;
|
||||
public BigDecimal totalWrittenOff;
|
||||
@Schema(example = "200.00")
|
||||
public Double totalRepaymentExpected;
|
||||
public BigDecimal totalRepaymentExpected;
|
||||
@Schema(example = "200.00")
|
||||
public Double totalPaidInAdvance;
|
||||
public BigDecimal totalPaidInAdvance;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalPaidLate;
|
||||
public BigDecimal totalPaidLate;
|
||||
@Schema(example = "0.00")
|
||||
public Double totalOutstanding;
|
||||
public BigDecimal totalOutstanding;
|
||||
public List<GetLoansLoanIdRepaymentPeriod> periods;
|
||||
}
|
||||
|
||||
@@ -332,71 +332,71 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "30")
|
||||
public Long daysInPeriod;
|
||||
@Schema(example = "200.000000")
|
||||
public Double principalOriginalDue;
|
||||
public BigDecimal principalOriginalDue;
|
||||
@Schema(example = "200.000000")
|
||||
public Double principalDue;
|
||||
public BigDecimal principalDue;
|
||||
@Schema(example = "200.000000")
|
||||
public Double principalPaid;
|
||||
public BigDecimal principalPaid;
|
||||
@Schema(example = "0.000000")
|
||||
public Double principalWrittenOff;
|
||||
public BigDecimal principalWrittenOff;
|
||||
@Schema(example = "20.000000")
|
||||
public Double principalOutstanding;
|
||||
public BigDecimal principalOutstanding;
|
||||
@Schema(example = "20.000000")
|
||||
public Double principalLoanBalanceOutstanding;
|
||||
public BigDecimal principalLoanBalanceOutstanding;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestOriginalDue;
|
||||
public BigDecimal interestOriginalDue;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestDue;
|
||||
public BigDecimal interestDue;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestPaid;
|
||||
public BigDecimal interestPaid;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestWaived;
|
||||
public BigDecimal interestWaived;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestWrittenOff;
|
||||
public BigDecimal interestWrittenOff;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestOutstanding;
|
||||
public BigDecimal interestOutstanding;
|
||||
@Schema(example = "0.000000")
|
||||
public Double feeChargesDue;
|
||||
public BigDecimal feeChargesDue;
|
||||
@Schema(example = "20.000000")
|
||||
public Double feeChargesPaid;
|
||||
public BigDecimal feeChargesPaid;
|
||||
@Schema(example = "20.000000")
|
||||
public Double feeChargesWaived;
|
||||
public BigDecimal feeChargesWaived;
|
||||
@Schema(example = "20.000000")
|
||||
public Double feeChargesWrittenOff;
|
||||
public BigDecimal feeChargesWrittenOff;
|
||||
@Schema(example = "20.000000")
|
||||
public Double feeChargesOutstanding;
|
||||
public BigDecimal feeChargesOutstanding;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesDue;
|
||||
public BigDecimal penaltyChargesDue;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesPaid;
|
||||
public BigDecimal penaltyChargesPaid;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesWaived;
|
||||
public BigDecimal penaltyChargesWaived;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesWrittenOff;
|
||||
public BigDecimal penaltyChargesWrittenOff;
|
||||
@Schema(example = "20.000000")
|
||||
public Double penaltyChargesOutstanding;
|
||||
public BigDecimal penaltyChargesOutstanding;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalOriginalDueForPeriod;
|
||||
public BigDecimal totalOriginalDueForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalDueForPeriod;
|
||||
public BigDecimal totalDueForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalPaidForPeriod;
|
||||
public BigDecimal totalPaidForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalPaidInAdvanceForPeriod;
|
||||
public BigDecimal totalPaidInAdvanceForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalPaidLateForPeriod;
|
||||
public BigDecimal totalPaidLateForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalWaivedForPeriod;
|
||||
public BigDecimal totalWaivedForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalWrittenOffForPeriod;
|
||||
public BigDecimal totalWrittenOffForPeriod;
|
||||
@Schema(example = "200.000000")
|
||||
public Double totalOutstandingForPeriod;
|
||||
public BigDecimal totalOutstandingForPeriod;
|
||||
@Schema(example = "20.000000")
|
||||
public Double totalActualCostOfLoanForPeriod;
|
||||
public BigDecimal totalActualCostOfLoanForPeriod;
|
||||
@Schema(example = "200.000000")
|
||||
public Double totalInstallmentAmountForPeriod;
|
||||
public BigDecimal totalInstallmentAmountForPeriod;
|
||||
@Schema(example = "2.000000")
|
||||
public Double totalCredits;
|
||||
public BigDecimal totalCredits;
|
||||
@Schema(example = "true")
|
||||
public Boolean downPaymentPeriod;
|
||||
}
|
||||
@@ -412,15 +412,15 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate actualDisbursementDate;
|
||||
@Schema(example = "22000.000000")
|
||||
public Double principal;
|
||||
public BigDecimal principal;
|
||||
@Schema(example = "22000.000000")
|
||||
public Double netDisbursalAmount;
|
||||
public BigDecimal netDisbursalAmount;
|
||||
@Schema(example = "1")
|
||||
public String loanChargeId;
|
||||
@Schema(example = "22000.000000")
|
||||
public Double chargeAmount;
|
||||
public BigDecimal chargeAmount;
|
||||
@Schema(example = "22000.000000")
|
||||
public Double waivedChargeAmount;
|
||||
public BigDecimal waivedChargeAmount;
|
||||
@Schema(example = "dd MMMM yyyy")
|
||||
public String dateFormat;
|
||||
@Schema(example = "de_DE")
|
||||
@@ -517,77 +517,81 @@ final class LoansApiResourceSwagger {
|
||||
|
||||
public GetLoansLoanIdCurrency currency;
|
||||
@Schema(example = "1000000.000000")
|
||||
public Double principalDisbursed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double principalPaid;
|
||||
@Schema(example = "0.00")
|
||||
public Double principalAdjustments;
|
||||
@Schema(example = "0.000000")
|
||||
public Double principalWrittenOff;
|
||||
public BigDecimal totalPrincipal;
|
||||
@Schema(example = "1000000.000000")
|
||||
public Double principalOutstanding;
|
||||
public BigDecimal principalDisbursed;
|
||||
@Schema(example = "1000000.000000")
|
||||
public BigDecimal totalPrincipalFromCapitalizedIncome;
|
||||
@Schema(example = "0.000000")
|
||||
public BigDecimal principalPaid;
|
||||
@Schema(example = "0.00")
|
||||
public BigDecimal principalAdjustments;
|
||||
@Schema(example = "0.000000")
|
||||
public BigDecimal principalWrittenOff;
|
||||
@Schema(example = "1000000.000000")
|
||||
public BigDecimal principalOutstanding;
|
||||
@Schema(example = "833333.300000")
|
||||
public Double principalOverdue;
|
||||
public BigDecimal principalOverdue;
|
||||
@Schema(example = "240000.000000")
|
||||
public Double interestCharged;
|
||||
public BigDecimal interestCharged;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestPaid;
|
||||
public BigDecimal interestPaid;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestWaived;
|
||||
public BigDecimal interestWaived;
|
||||
@Schema(example = "0.000000")
|
||||
public Double interestWrittenOff;
|
||||
public BigDecimal interestWrittenOff;
|
||||
@Schema(example = "240000.000000")
|
||||
public Double interestOutstanding;
|
||||
public BigDecimal interestOutstanding;
|
||||
@Schema(example = "200000.000000")
|
||||
public Double interestOverdue;
|
||||
public BigDecimal interestOverdue;
|
||||
@Schema(example = "0.00")
|
||||
public Double feeAdjustments;
|
||||
public BigDecimal feeAdjustments;
|
||||
@Schema(example = "18000.000000")
|
||||
public Double feeChargesCharged;
|
||||
public BigDecimal feeChargesCharged;
|
||||
@Schema(example = "0.000000")
|
||||
public Double feeChargesDueAtDisbursementCharged;
|
||||
public BigDecimal feeChargesDueAtDisbursementCharged;
|
||||
@Schema(example = "0.000000")
|
||||
public Double feeChargesPaid;
|
||||
public BigDecimal feeChargesPaid;
|
||||
@Schema(example = "0.000000")
|
||||
public Double feeChargesWaived;
|
||||
public BigDecimal feeChargesWaived;
|
||||
@Schema(example = "0.000000")
|
||||
public Double feeChargesWrittenOff;
|
||||
public BigDecimal feeChargesWrittenOff;
|
||||
@Schema(example = "18000.000000")
|
||||
public Double feeChargesOutstanding;
|
||||
public BigDecimal feeChargesOutstanding;
|
||||
@Schema(example = "15000.000000")
|
||||
public Double feeChargesOverdue;
|
||||
public BigDecimal feeChargesOverdue;
|
||||
@Schema(example = "0.00")
|
||||
public Double penaltyAdjustments;
|
||||
public BigDecimal penaltyAdjustments;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesCharged;
|
||||
public BigDecimal penaltyChargesCharged;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesPaid;
|
||||
public BigDecimal penaltyChargesPaid;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesWaived;
|
||||
public BigDecimal penaltyChargesWaived;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesWrittenOff;
|
||||
public BigDecimal penaltyChargesWrittenOff;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesOutstanding;
|
||||
public BigDecimal penaltyChargesOutstanding;
|
||||
@Schema(example = "0.000000")
|
||||
public Double penaltyChargesOverdue;
|
||||
public BigDecimal penaltyChargesOverdue;
|
||||
@Schema(example = "1258000.000000")
|
||||
public Double totalExpectedRepayment;
|
||||
public BigDecimal totalExpectedRepayment;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalRepayment;
|
||||
public BigDecimal totalRepayment;
|
||||
@Schema(example = "258000.000000")
|
||||
public Double totalExpectedCostOfLoan;
|
||||
public BigDecimal totalExpectedCostOfLoan;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalCostOfLoan;
|
||||
public BigDecimal totalCostOfLoan;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalWaived;
|
||||
public BigDecimal totalWaived;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalWrittenOff;
|
||||
public BigDecimal totalWrittenOff;
|
||||
@Schema(example = "1258000.000000")
|
||||
public Double totalOutstanding;
|
||||
public BigDecimal totalOutstanding;
|
||||
@Schema(example = "1048333.30000")
|
||||
public Double totalOverdue;
|
||||
public BigDecimal totalOverdue;
|
||||
@Schema(example = "2456.30000")
|
||||
public Double totalRecovered;
|
||||
public BigDecimal totalRecovered;
|
||||
@Schema(example = "[2012, 5, 10]")
|
||||
public LocalDate overdueSinceDate;
|
||||
@Schema(example = "1")
|
||||
@@ -597,9 +601,9 @@ final class LoansApiResourceSwagger {
|
||||
public GetLoansLoanIdLinkedAccount linkedAccount;
|
||||
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
|
||||
@Schema(example = "1100.000000")
|
||||
public Double fixedEmiAmount;
|
||||
public BigDecimal fixedEmiAmount;
|
||||
@Schema(example = "35000.000000")
|
||||
public Double maxOutstandingLoanBalance;
|
||||
public BigDecimal maxOutstandingLoanBalance;
|
||||
@Schema(example = "false")
|
||||
public Boolean canDisburse;
|
||||
@Schema(example = "true")
|
||||
@@ -607,33 +611,33 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "false")
|
||||
public Boolean isNPA;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalMerchantRefund;
|
||||
public BigDecimal totalMerchantRefund;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalMerchantRefundReversed;
|
||||
public BigDecimal totalMerchantRefundReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalPayoutRefund;
|
||||
public BigDecimal totalPayoutRefund;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalPayoutRefundReversed;
|
||||
public BigDecimal totalPayoutRefundReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalGoodwillCredit;
|
||||
public BigDecimal totalGoodwillCredit;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalGoodwillCreditReversed;
|
||||
public BigDecimal totalGoodwillCreditReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalChargeAdjustment;
|
||||
public BigDecimal totalChargeAdjustment;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalChargeAdjustmentReversed;
|
||||
public BigDecimal totalChargeAdjustmentReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalChargeback;
|
||||
public BigDecimal totalChargeback;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalCreditBalanceRefund;
|
||||
public BigDecimal totalCreditBalanceRefund;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalCreditBalanceRefundReversed;
|
||||
public BigDecimal totalCreditBalanceRefundReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalRepaymentTransaction;
|
||||
public BigDecimal totalRepaymentTransaction;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalInterestPaymentWaiver;
|
||||
public BigDecimal totalInterestPaymentWaiver;
|
||||
@Schema(example = "0.000000")
|
||||
public Double totalRepaymentTransactionReversed;
|
||||
public BigDecimal totalRepaymentTransactionReversed;
|
||||
@Schema(example = "0.000000")
|
||||
public BigDecimal totalUnpaidPayableDueInterest;
|
||||
@Schema(example = "0.000000")
|
||||
@@ -754,7 +758,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "11")
|
||||
public Long id;
|
||||
@Schema(example = "100.000000")
|
||||
public Double amount;
|
||||
public BigDecimal amount;
|
||||
@Schema(example = "9679")
|
||||
public Integer installmentNumber;
|
||||
@Schema(example = "1")
|
||||
@@ -810,7 +814,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "CHARGEBACK")
|
||||
public String relationType;
|
||||
@Schema(example = "100.00")
|
||||
public Double amount;
|
||||
public BigDecimal amount;
|
||||
@Schema(example = "Repayment Adjustment Chargeback")
|
||||
public String paymentType;
|
||||
|
||||
@@ -831,27 +835,27 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(description = "Payment detail")
|
||||
public GetLoansLoanIdPaymentDetailData paymentDetailData;
|
||||
@Schema(example = "100.000000")
|
||||
public Double amount;
|
||||
public BigDecimal amount;
|
||||
@Schema(example = "100.000000")
|
||||
public Double netDisbursalAmount;
|
||||
public BigDecimal netDisbursalAmount;
|
||||
@Schema(example = "100.000000")
|
||||
public Double principalPortion;
|
||||
public BigDecimal principalPortion;
|
||||
@Schema(example = "100.000000")
|
||||
public Double interestPortion;
|
||||
public BigDecimal interestPortion;
|
||||
@Schema(example = "100.000000")
|
||||
public Double feeChargesPortion;
|
||||
public BigDecimal feeChargesPortion;
|
||||
@Schema(example = "100.000000")
|
||||
public Double penaltyChargesPortion;
|
||||
public BigDecimal penaltyChargesPortion;
|
||||
@Schema(example = "100.000000")
|
||||
public Double overpaymentPortion;
|
||||
public BigDecimal overpaymentPortion;
|
||||
@Schema(example = "100.000000")
|
||||
public Double unrecognizedIncomePortion;
|
||||
public BigDecimal unrecognizedIncomePortion;
|
||||
@Schema(example = "3")
|
||||
public String externalId;
|
||||
@Schema(example = "100.000000")
|
||||
public Double fixedEmiAmount;
|
||||
public BigDecimal fixedEmiAmount;
|
||||
@Schema(example = "100.000000")
|
||||
public Double outstandingLoanBalance;
|
||||
public BigDecimal outstandingLoanBalance;
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate submittedOnDate;
|
||||
public boolean manuallyReversed;
|
||||
@@ -870,7 +874,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "de_DE")
|
||||
public String locale;
|
||||
@Schema(example = "100.000000")
|
||||
public Double transactionAmount;
|
||||
public BigDecimal transactionAmount;
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate transactionDate;
|
||||
@Schema(example = "101")
|
||||
@@ -924,19 +928,19 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate dueDate;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amount;
|
||||
public BigDecimal amount;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amountOutstanding;
|
||||
public BigDecimal amountOutstanding;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amountWaived;
|
||||
public BigDecimal amountWaived;
|
||||
@Schema(example = "false")
|
||||
public boolean paid;
|
||||
@Schema(example = "false")
|
||||
public boolean waived;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amountAccrued;
|
||||
public BigDecimal amountAccrued;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amountUnrecognized;
|
||||
public BigDecimal amountUnrecognized;
|
||||
}
|
||||
|
||||
@Schema(example = "3")
|
||||
@@ -952,23 +956,23 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(description = "Enum option data")
|
||||
public GetLoansLoanIdEnumOptionData chargeCalculationType;
|
||||
@Schema(example = "3.400000")
|
||||
public Double percentage;
|
||||
public BigDecimal percentage;
|
||||
@Schema(example = "13.560000")
|
||||
public Double amountPercentageAppliedTo;
|
||||
public BigDecimal amountPercentageAppliedTo;
|
||||
@Schema(description = "currency")
|
||||
public GetLoansLoanIdCurrency currency;
|
||||
@Schema(example = "102.000000")
|
||||
public Double amount;
|
||||
public BigDecimal amount;
|
||||
@Schema(example = "12.000000")
|
||||
public Double amountPaid;
|
||||
public BigDecimal amountPaid;
|
||||
@Schema(example = "14.000000")
|
||||
public Double amountWaived;
|
||||
public BigDecimal amountWaived;
|
||||
@Schema(example = "102.000000")
|
||||
public Double amountWrittenOff;
|
||||
public BigDecimal amountWrittenOff;
|
||||
@Schema(example = "102.000000")
|
||||
public Double amountOutstanding;
|
||||
public BigDecimal amountOutstanding;
|
||||
@Schema(example = "102.000000")
|
||||
public Double amountOrPercentage;
|
||||
public BigDecimal amountOrPercentage;
|
||||
@Schema(example = "false")
|
||||
public boolean penalty;
|
||||
@Schema(description = "Enum option data")
|
||||
@@ -982,15 +986,15 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "3")
|
||||
public Long loanId;
|
||||
@Schema(example = "30.000000")
|
||||
public Double minCap;
|
||||
public BigDecimal minCap;
|
||||
@Schema(example = "30.000000")
|
||||
public Double maxCap;
|
||||
public BigDecimal maxCap;
|
||||
@Schema(description = "List of GetLoansLoanIdLoanInstallmentChargeData")
|
||||
public List<GetLoansLoanIdLoanInstallmentChargeData> installmentChargeData;
|
||||
@Schema(example = "30.000000")
|
||||
private Double amountAccrued;
|
||||
private BigDecimal amountAccrued;
|
||||
@Schema(example = "30.000000")
|
||||
private Double amountUnrecognized;
|
||||
private BigDecimal amountUnrecognized;
|
||||
@Schema(example = "3ert3453")
|
||||
private String externalId;
|
||||
}
|
||||
@@ -1000,7 +1004,7 @@ final class LoansApiResourceSwagger {
|
||||
private GetLoansLoanIdDelinquencySummary() {}
|
||||
|
||||
@Schema(example = "100.000000")
|
||||
public Double availableDisbursementAmount;
|
||||
public BigDecimal availableDisbursementAmount;
|
||||
@Schema(example = "12")
|
||||
public Integer pastDueDays;
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
@@ -1010,7 +1014,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate delinquentDate;
|
||||
@Schema(example = "100.000000")
|
||||
public Double delinquentAmount;
|
||||
public BigDecimal delinquentAmount;
|
||||
@Schema(example = "80.000000")
|
||||
public BigDecimal delinquentPrincipal;
|
||||
@Schema(example = "10.000000")
|
||||
@@ -1022,12 +1026,12 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate lastPaymentDate;
|
||||
@Schema(example = "100.000000")
|
||||
public Double lastPaymentAmount;
|
||||
public BigDecimal lastPaymentAmount;
|
||||
|
||||
@Schema(example = "[2022, 07, 01]")
|
||||
public LocalDate lastRepaymentDate;
|
||||
@Schema(example = "100.000000")
|
||||
public Double lastRepaymentAmount;
|
||||
public BigDecimal lastRepaymentAmount;
|
||||
|
||||
@Schema(description = "List of GetLoansLoanIdDelinquencyPausePeriod")
|
||||
public List<GetLoansLoanIdDelinquencyPausePeriod> delinquencyPausePeriods;
|
||||
@@ -1094,7 +1098,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2024, 1, 1]")
|
||||
public LocalDate termVariationApplicableFrom;
|
||||
@Schema(example = "200.000000")
|
||||
public Double decimalValue;
|
||||
public BigDecimal decimalValue;
|
||||
@Schema(example = "[2024, 1, 1]")
|
||||
public LocalDate dateValue;
|
||||
@Schema(example = "false")
|
||||
@@ -1139,11 +1143,11 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "1000000.00")
|
||||
public BigDecimal principal;
|
||||
@Schema(example = "1000.000000")
|
||||
public Double approvedPrincipal;
|
||||
public BigDecimal approvedPrincipal;
|
||||
@Schema(example = "1001.000000")
|
||||
public Double proposedPrincipal;
|
||||
public BigDecimal proposedPrincipal;
|
||||
@Schema(example = "200.000000")
|
||||
public Double netDisbursalAmount;
|
||||
public BigDecimal netDisbursalAmount;
|
||||
@Schema(example = "12")
|
||||
public Integer termFrequency;
|
||||
public GetLoansLoanIdTermPeriodFrequencyType termPeriodFrequencyType;
|
||||
@@ -1185,7 +1189,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "false")
|
||||
public Boolean enableInstallmentLevelDelinquency;
|
||||
@Schema(example = "250.000000")
|
||||
public Double totalOverpaid;
|
||||
public BigDecimal totalOverpaid;
|
||||
public LocalDate lastClosedBusinessDate;
|
||||
@Schema(example = "[2013, 11, 1]")
|
||||
public LocalDate overpaidOnDate;
|
||||
@@ -1240,7 +1244,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "1 November 2023")
|
||||
public String expectedDisbursementDate;
|
||||
@Schema(example = "1000.00")
|
||||
public Double principal;
|
||||
public BigDecimal principal;
|
||||
}
|
||||
|
||||
private PostLoansRequest() {}
|
||||
@@ -1387,7 +1391,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "0")
|
||||
public Long totalPrincipalPaid;
|
||||
@Schema(example = "13471.52")
|
||||
public Double totalInterestCharged;
|
||||
public BigDecimal totalInterestCharged;
|
||||
@Schema(example = "0")
|
||||
public Long totalFeeChargesCharged;
|
||||
@Schema(example = "0")
|
||||
@@ -1397,7 +1401,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "0")
|
||||
public Long totalWrittenOff;
|
||||
@Schema(example = "113471.52")
|
||||
public Double totalRepaymentExpected;
|
||||
public BigDecimal totalRepaymentExpected;
|
||||
@Schema(example = "0")
|
||||
public Long totalRepayment;
|
||||
@Schema(example = "0")
|
||||
@@ -1616,7 +1620,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(example = "[2012, 4, 3]")
|
||||
public LocalDate expectedDisbursementDate;
|
||||
@Schema(example = "22000")
|
||||
public Double principal;
|
||||
public BigDecimal principal;
|
||||
}
|
||||
|
||||
@Schema(example = "2")
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class LoanSchedulePeriodDataWrapper {
|
||||
|
||||
private final LoanPrincipalRelatedDataHolder data;
|
||||
private final LocalDate date;
|
||||
private final boolean isDisbursement;
|
||||
}
|
||||
+2
-1
@@ -97,7 +97,8 @@ public abstract class CommonLoanSummaryDataProvider implements LoanSummaryDataPr
|
||||
}
|
||||
|
||||
return LoanSummaryData.builder().currency(defaultSummaryData.getCurrency())
|
||||
.principalDisbursed(defaultSummaryData.getPrincipalDisbursed())
|
||||
.principalDisbursed(defaultSummaryData.getPrincipalDisbursed()).totalPrincipal(defaultSummaryData.getTotalPrincipal())
|
||||
.totalPrincipalFromCapitalizedIncome(defaultSummaryData.getTotalPrincipalFromCapitalizedIncome())
|
||||
.principalAdjustments(defaultSummaryData.getPrincipalAdjustments()).principalPaid(defaultSummaryData.getPrincipalPaid())
|
||||
.principalWrittenOff(defaultSummaryData.getPrincipalWrittenOff())
|
||||
.principalOutstanding(defaultSummaryData.getPrincipalOutstanding())
|
||||
|
||||
+19
-9
@@ -21,10 +21,12 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
@@ -51,9 +53,10 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
@Transactional
|
||||
public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull final Loan loan) {
|
||||
final LocalDate transactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
|
||||
final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, false);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction));
|
||||
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
|
||||
false);
|
||||
amortizationTransaction.ifPresent(loanTransaction -> businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(loanTransaction)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -67,13 +70,16 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
transactionDate = DateUtils.getBusinessLocalDate();
|
||||
}
|
||||
|
||||
final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, true);
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction));
|
||||
final Optional<LoanTransaction> amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate,
|
||||
true);
|
||||
if (amortizationTransaction.isPresent()) {
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
}
|
||||
}
|
||||
|
||||
private LoanTransaction createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
|
||||
private Optional<LoanTransaction> createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
|
||||
final boolean isChargeOff) {
|
||||
ExternalId externalId = ExternalId.empty();
|
||||
BigDecimal totalUnrecognizedAmount = BigDecimal.ZERO;
|
||||
@@ -92,13 +98,17 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
|
||||
balance.setUnrecognizedAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
if (MathUtil.isLessThanOrEqualZero(totalUnrecognizedAmount)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final LoanTransaction amortizationTransaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(),
|
||||
transactionDate, totalUnrecognizedAmount, externalId);
|
||||
|
||||
loan.addLoanTransaction(amortizationTransaction);
|
||||
loanTransactionRepository.saveAndFlush(amortizationTransaction);
|
||||
|
||||
return amortizationTransaction;
|
||||
return Optional.of(amortizationTransaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+105
-21
@@ -98,6 +98,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanSchedulePeriodDataWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||
@@ -118,6 +119,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
@@ -249,8 +251,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
accountData.getFeeChargesAtDisbursementCharged());
|
||||
|
||||
final Collection<DisbursementData> disbursementData = retrieveLoanDisbursementDetails(accountData.getId());
|
||||
List<LoanTransactionRepaymentPeriodData> capitalizedIncomeData = loanCapitalizedIncomeBalanceRepository
|
||||
.findRepaymentPeriodDataByLoanId(accountData.getId());
|
||||
final LoanScheduleData repaymentSchedule = retrieveRepaymentSchedule(accountData.getId(), repaymentScheduleRelatedData,
|
||||
disbursementData, accountData.isInterestRecalculationEnabled(),
|
||||
disbursementData, capitalizedIncomeData, accountData.isInterestRecalculationEnabled(),
|
||||
LoanScheduleType.fromEnumOptionData(accountData.getLoanScheduleType()));
|
||||
accountData.setRepaymentSchedule(repaymentSchedule);
|
||||
return accountData;
|
||||
@@ -259,13 +263,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
@Override
|
||||
public LoanScheduleData retrieveRepaymentSchedule(final Long loanId,
|
||||
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, Collection<DisbursementData> disbursementData,
|
||||
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
|
||||
Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData, boolean isInterestRecalculationEnabled,
|
||||
LoanScheduleType loanScheduleType) {
|
||||
|
||||
try {
|
||||
this.context.authenticatedUser();
|
||||
|
||||
final LoanScheduleResultSetExtractor fullResultsetExtractor = new LoanScheduleResultSetExtractor(
|
||||
repaymentScheduleRelatedLoanData, disbursementData, isInterestRecalculationEnabled, loanScheduleType);
|
||||
repaymentScheduleRelatedLoanData, disbursementData, capitalizedIncomeData, isInterestRecalculationEnabled,
|
||||
loanScheduleType);
|
||||
final String sql = "select " + fullResultsetExtractor.schema() + " where ls.loan_id = ? order by ls.loan_id, ls.installment";
|
||||
|
||||
return this.jdbcTemplate.query(sql, fullResultsetExtractor, loanId); // NOSONAR
|
||||
@@ -749,7 +755,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
+ " l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate, dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname,"
|
||||
+ " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, "
|
||||
+ " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate, "
|
||||
+ " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.net_disbursal_amount as netDisbursalAmount, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
|
||||
+ " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.total_principal_derived as totalPrincipal, l.approved_principal as approvedPrincipal, l.net_disbursal_amount as netDisbursalAmount, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery,"
|
||||
+ " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing,"
|
||||
+ " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, "
|
||||
+ " l.repayment_period_frequency_enum as repaymentFrequencyType, l.interest_period_frequency_enum as interestRateFrequencyType, "
|
||||
@@ -764,7 +770,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
+ sqlGenerator.escape("name")
|
||||
+ " as currencyName, rc.display_symbol as currencyDisplaySymbol, rc.internationalized_name_code as currencyNameCode, "
|
||||
+ " l.loan_officer_id as loanOfficerId, s.display_name as loanOfficerName, "
|
||||
+ " l.principal_disbursed_derived as principalDisbursed, l.principal_repaid_derived as principalPaid,"
|
||||
+ " l.principal_disbursed_derived as principalDisbursed, l.capitalized_income_derived as totalPrincipalFromCapitalizedIncome, l.principal_repaid_derived as principalPaid,"
|
||||
+ " l.principal_adjustments_derived as principalAdjustments, l.principal_writtenoff_derived as principalWrittenOff,"
|
||||
+ " l.fee_adjustments_derived as feeAdjustments, l.penalty_adjustments_derived as penaltyAdjustments,"
|
||||
+ " l.principal_outstanding_derived as principalOutstanding, l.interest_charged_derived as interestCharged,"
|
||||
@@ -1023,6 +1029,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
|
||||
// loan summary
|
||||
final BigDecimal principalDisbursed = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalDisbursed");
|
||||
final BigDecimal totalPrincipal = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "totalPrincipal");
|
||||
final BigDecimal totalPrincipalFromCapitalizedIncome = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs,
|
||||
"totalPrincipalFromCapitalizedIncome");
|
||||
final BigDecimal principalAdjustments = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalAdjustments");
|
||||
final BigDecimal principalPaid = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalPaid");
|
||||
final BigDecimal principalWrittenOff = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalWrittenOff");
|
||||
@@ -1066,6 +1075,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
inArrears = (overdueSinceDate != null);
|
||||
|
||||
loanSummary = LoanSummaryData.builder().currency(currencyData).principalDisbursed(principalDisbursed)
|
||||
.totalPrincipal(totalPrincipal).totalPrincipalFromCapitalizedIncome(totalPrincipalFromCapitalizedIncome)
|
||||
.principalAdjustments(principalAdjustments).principalPaid(principalPaid).principalWrittenOff(principalWrittenOff)
|
||||
.principalOutstanding(principalOutstanding).principalOverdue(principalOverdue).interestCharged(interestCharged)
|
||||
.interestPaid(interestPaid).interestWaived(interestWaived).interestWrittenOff(interestWrittenOff)
|
||||
@@ -1271,15 +1281,18 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
private final DisbursementData disbursement;
|
||||
private final BigDecimal totalFeeChargesDueAtDisbursement;
|
||||
private final Collection<DisbursementData> disbursementData;
|
||||
private final Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData;
|
||||
private final LoanScheduleType loanScheduleType;
|
||||
private LocalDate lastDueDate;
|
||||
private BigDecimal outstandingLoanPrincipalBalance;
|
||||
private boolean excludePastUnDisbursed;
|
||||
|
||||
LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData,
|
||||
Collection<DisbursementData> disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
|
||||
Collection<DisbursementData> disbursementData, Collection<LoanTransactionRepaymentPeriodData> capitalizedIncomeData,
|
||||
boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) {
|
||||
this.currency = repaymentScheduleRelatedLoanData.getCurrency();
|
||||
this.disbursement = repaymentScheduleRelatedLoanData.disbursementData();
|
||||
this.capitalizedIncomeData = capitalizedIncomeData;
|
||||
this.totalFeeChargesDueAtDisbursement = repaymentScheduleRelatedLoanData.getTotalFeeChargesAtDisbursement();
|
||||
this.lastDueDate = this.disbursement.disbursementDate();
|
||||
this.outstandingLoanPrincipalBalance = this.disbursement.getPrincipal();
|
||||
@@ -1359,10 +1372,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "dueDate");
|
||||
final LocalDate obligationsMetOnDate = JdbcSupport.getLocalDate(rs, "obligationsMetOnDate");
|
||||
final boolean complete = rs.getBoolean("complete");
|
||||
BigDecimal disbursedAmount = BigDecimal.ZERO;
|
||||
|
||||
disbursedAmount = processDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, disbursementPeriodIds,
|
||||
disbursementChargeAmount, waivedChargeAmount, periods);
|
||||
List<LoanSchedulePeriodDataWrapper> combinedDataList = new ArrayList<>();
|
||||
combinedDataList.addAll(
|
||||
collectEligibleDisbursementData(loanScheduleType, disbursementData, fromDate, dueDate, disbursementPeriodIds));
|
||||
combinedDataList.addAll(collectEligibleCapitalizedIncomeData(fromDate, dueDate, disbursementPeriodIds));
|
||||
combinedDataList.sort(this::sortPeriodDataHolders);
|
||||
fillLoanSchedulePeriodData(periods, combinedDataList, disbursementChargeAmount, waivedChargeAmount);
|
||||
|
||||
BigDecimal disbursedAmount = calculateDisbursedAmount(combinedDataList);
|
||||
|
||||
// Add the Charge back or Credits to the initial amount to avoid negative balance
|
||||
final BigDecimal principalCredits = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "principalCredits");
|
||||
@@ -1473,26 +1491,82 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
totalPaidLate.getAmount(), totalOutstanding.getAmount(), totalCredits.getAmount());
|
||||
}
|
||||
|
||||
private BigDecimal processDisbursementData(LoanScheduleType loanScheduleType, Collection<DisbursementData> disbursementData,
|
||||
LocalDate fromDate, LocalDate dueDate, Set<Long> disbursementPeriodIds, BigDecimal disbursementChargeAmount,
|
||||
BigDecimal waivedChargeAmount, List<LoanSchedulePeriodData> periods) {
|
||||
BigDecimal disbursedAmount = BigDecimal.ZERO;
|
||||
private List<LoanSchedulePeriodDataWrapper> collectEligibleDisbursementData(LoanScheduleType loanScheduleType,
|
||||
Collection<DisbursementData> disbursementData, LocalDate fromDate, LocalDate dueDate, Set<Long> disbursementPeriodIds) {
|
||||
List<LoanSchedulePeriodDataWrapper> disbursementDataList = new ArrayList<>();
|
||||
// Collect eligible disbursement data
|
||||
for (final DisbursementData data : disbursementData) {
|
||||
boolean isDueForDisbursement = data.isDueForDisbursement(loanScheduleType, fromDate, dueDate);
|
||||
if (((fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate))
|
||||
boolean isEligible = ((fromDate.equals(this.disbursement.disbursementDate()) && data.disbursementDate().equals(fromDate))
|
||||
|| (fromDate.equals(dueDate) && data.disbursementDate().equals(fromDate))
|
||||
|| canAddDisbursementData(data, isDueForDisbursement, excludePastUnDisbursed))
|
||||
&& !disbursementPeriodIds.contains(data.getId())) {
|
||||
disbursedAmount = disbursedAmount.add(data.getPrincipal());
|
||||
LoanSchedulePeriodData periodData = createLoanSchedulePeriodData(data, disbursementChargeAmount, waivedChargeAmount);
|
||||
periods.add(periodData);
|
||||
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(periodData.getPrincipalDisbursed());
|
||||
&& !disbursementPeriodIds.contains(data.getId());
|
||||
|
||||
if (isEligible) {
|
||||
disbursementDataList.add(new LoanSchedulePeriodDataWrapper(data, data.disbursementDate(), true));
|
||||
disbursementPeriodIds.add(data.getId());
|
||||
}
|
||||
}
|
||||
return disbursementDataList;
|
||||
}
|
||||
|
||||
private List<LoanSchedulePeriodDataWrapper> collectEligibleCapitalizedIncomeData(LocalDate fromDate, LocalDate dueDate,
|
||||
Set<Long> disbursementPeriodIds) {
|
||||
List<LoanSchedulePeriodDataWrapper> capitalizedIncomeDataList = new ArrayList<>();
|
||||
// Collect eligible capitalized income data
|
||||
for (LoanTransactionRepaymentPeriodData data : capitalizedIncomeData) {
|
||||
boolean isEligible = canAddCapitalizedIncomeData(data, fromDate, dueDate)
|
||||
&& !disbursementPeriodIds.contains(data.getTransactionId());
|
||||
|
||||
if (isEligible) {
|
||||
capitalizedIncomeDataList.add(new LoanSchedulePeriodDataWrapper(data, data.getDate(), false));
|
||||
disbursementPeriodIds.add(data.getTransactionId());
|
||||
}
|
||||
}
|
||||
return capitalizedIncomeDataList;
|
||||
}
|
||||
|
||||
private void fillLoanSchedulePeriodData(List<LoanSchedulePeriodData> periods, List<LoanSchedulePeriodDataWrapper> combinedDataList,
|
||||
BigDecimal disbursementChargeAmount, BigDecimal waivedChargeAmount) {
|
||||
// Process all collected data in chronological order
|
||||
for (LoanSchedulePeriodDataWrapper dataItem : combinedDataList) {
|
||||
LoanSchedulePeriodData periodData;
|
||||
if (dataItem.isDisbursement()) {
|
||||
// Process disbursement data
|
||||
DisbursementData data = (DisbursementData) dataItem.getData();
|
||||
periodData = createLoanSchedulePeriodData(data, disbursementChargeAmount, waivedChargeAmount);
|
||||
} else {
|
||||
// Process capitalized income data
|
||||
LoanTransactionRepaymentPeriodData data = (LoanTransactionRepaymentPeriodData) dataItem.getData();
|
||||
periodData = createLoanSchedulePeriodData(data);
|
||||
}
|
||||
|
||||
// Common processing for both data types
|
||||
periods.add(periodData);
|
||||
this.outstandingLoanPrincipalBalance = this.outstandingLoanPrincipalBalance.add(periodData.getPrincipalDisbursed());
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal calculateDisbursedAmount(List<LoanSchedulePeriodDataWrapper> combinedDataList) {
|
||||
BigDecimal disbursedAmount = BigDecimal.ZERO;
|
||||
for (LoanSchedulePeriodDataWrapper dataItem : combinedDataList) {
|
||||
if (dataItem.isDisbursement()) {
|
||||
DisbursementData data = (DisbursementData) dataItem.getData();
|
||||
disbursedAmount = disbursedAmount.add(data.getPrincipal());
|
||||
}
|
||||
}
|
||||
return disbursedAmount;
|
||||
}
|
||||
|
||||
private int sortPeriodDataHolders(LoanSchedulePeriodDataWrapper item1, LoanSchedulePeriodDataWrapper item2) {
|
||||
int dateComparison = item1.getDate().compareTo(item2.getDate());
|
||||
if (dateComparison == 0 && item1.isDisbursement() != item2.isDisbursement()) {
|
||||
// If dates are equal, prioritize disbursement data
|
||||
return item1.isDisbursement() ? -1 : 1;
|
||||
}
|
||||
return dateComparison;
|
||||
}
|
||||
|
||||
private LoanSchedulePeriodData createLoanSchedulePeriodData(final DisbursementData data, BigDecimal disbursementChargeAmount,
|
||||
BigDecimal waivedChargeAmount) {
|
||||
BigDecimal chargeAmount = data.getChargeAmount() == null ? disbursementChargeAmount
|
||||
@@ -1501,11 +1575,20 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
data.isDisbursed());
|
||||
}
|
||||
|
||||
private LoanSchedulePeriodData createLoanSchedulePeriodData(final LoanTransactionRepaymentPeriodData data) {
|
||||
BigDecimal feeCharges = Objects.isNull(data.getFeeChargesPortion()) ? BigDecimal.ZERO : data.getFeeChargesPortion();
|
||||
return LoanSchedulePeriodData.disbursementOnlyPeriod(data.getDate(), data.getAmount(), feeCharges, !data.isReversed());
|
||||
}
|
||||
|
||||
private boolean canAddDisbursementData(DisbursementData data, boolean isDueForDisbursement, boolean excludePastUnDisbursed) {
|
||||
return (!excludePastUnDisbursed || data.isDisbursed() || !DateUtils.isBeforeBusinessDate(data.disbursementDate()))
|
||||
&& isDueForDisbursement;
|
||||
}
|
||||
|
||||
private boolean canAddCapitalizedIncomeData(LoanTransactionRepaymentPeriodData data, LocalDate fromDate, LocalDate dueDate) {
|
||||
return !data.isReversed() && DateUtils.isDateInRangeFromInclusiveToExclusive(fromDate, dueDate, data.getDate());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class LoanTransactionsMapper implements RowMapper<LoanTransactionData> {
|
||||
@@ -2168,7 +2251,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
|
||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||
final BigDecimal outstandingLoanBalance = loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency).getAmount();
|
||||
final Boolean isReversed = false;
|
||||
final Boolean isManuallyReversed = false;
|
||||
|
||||
final Money outStandingAmount = loanRepaymentScheduleInstallment.getTotalOutstanding(currency);
|
||||
|
||||
@@ -2178,7 +2261,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
loanRepaymentScheduleInstallment.getInterestOutstanding(currency).getAmount(),
|
||||
loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency).getAmount(),
|
||||
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, unrecognizedIncomePortion,
|
||||
paymentTypeOptions, ExternalId.empty(), null, null, outstandingLoanBalance, isReversed, loanId, loan.getExternalId());
|
||||
paymentTypeOptions, ExternalId.empty(), null, null, outstandingLoanBalance, isManuallyReversed, loanId,
|
||||
loan.getExternalId());
|
||||
}
|
||||
|
||||
private static final class CurrencyMapper implements RowMapper<CurrencyData> {
|
||||
|
||||
+9
-2
@@ -64,6 +64,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
||||
import org.apache.fineract.portfolio.note.domain.Note;
|
||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
@@ -93,6 +94,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
private final LoanChargeValidator loanChargeValidator;
|
||||
private final LoanJournalEntryPoster journalEntryPoster;
|
||||
private final LoanBalanceService loanBalanceService;
|
||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
|
||||
@@ -245,8 +247,9 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
transactionForAdjustment.getTransactionDate());
|
||||
|
||||
if (!transactionForAdjustment.isAccrualRelated() && transactionForAdjustment.isNotRepaymentLikeType()
|
||||
&& transactionForAdjustment.isNotWaiver() && transactionForAdjustment.isNotCreditBalanceRefund()) {
|
||||
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver, accrual or credit balance refund can be adjusted.";
|
||||
&& transactionForAdjustment.isNotWaiver() && transactionForAdjustment.isNotCreditBalanceRefund()
|
||||
&& !transactionForAdjustment.isCapitalizedIncome()) {
|
||||
final String errorMessage = "Only (non-reversed) transactions of type repayment, waiver, capitalized income, accrual or credit balance refund can be adjusted.";
|
||||
throw new InvalidLoanTransactionTypeException("transaction",
|
||||
"adjustment.is.only.allowed.to.repayment.or.waiver.or.creditbalancerefund.transactions", errorMessage);
|
||||
}
|
||||
@@ -285,6 +288,10 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, transactionForAdjustment,
|
||||
scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
if (transactionForAdjustment.getTypeOf().equals(LoanTransactionType.CAPITALIZED_INCOME)) {
|
||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||
}
|
||||
}
|
||||
|
||||
private Loan saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) {
|
||||
|
||||
+7
-3
@@ -204,9 +204,13 @@ public class NoteWritePlatformServiceJpaRepositoryImpl implements NoteWritePlatf
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createLoanNote(final Long loanId, final String note) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final Note newNote = Note.loanNote(loan, note);
|
||||
public void createLoanTransactionNote(final Long loanTransactionId, final String note) {
|
||||
final LoanTransaction loanTransaction = this.loanTransactionRepository.findById(loanTransactionId)
|
||||
.orElseThrow(() -> new LoanTransactionNotFoundException(loanTransactionId));
|
||||
|
||||
final Loan loan = loanTransaction.getLoan();
|
||||
|
||||
final Note newNote = Note.loanTransactionNote(loan, loanTransaction, note);
|
||||
|
||||
this.noteRepository.save(newNote);
|
||||
}
|
||||
|
||||
@@ -196,4 +196,5 @@
|
||||
<include file="parts/0175_add_fk_acc_product_mapping.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0176_add_capitalized_income_amortization_transaction.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0177_acc_journal_entry_index.xml" relativeToChangelogFile="true" />
|
||||
<include file="parts/0178_add_principal_from_capitalized_income_to_loan_summary.xml" relativeToChangelogFile="true" />
|
||||
</databaseChangeLog>
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_loan">
|
||||
<column defaultValueNumeric="0.000000" name="capitalized_income_derived" type="DECIMAL(19, 6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column defaultValueNumeric="0.000000" name="total_principal_derived" type="DECIMAL(19, 6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+23
-23
@@ -5938,7 +5938,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
// totalUnpaidPayableDueInterest → Total outstanding interest amount on all the periods that are before
|
||||
assertEquals(BigDecimal.valueOf(loanDetails.getSummary().getInterestCharged()).stripTrailingZeros(),
|
||||
assertEquals(loanDetails.getSummary().getInterestCharged().stripTrailingZeros(),
|
||||
loanDetails.getSummary().getTotalUnpaidPayableDueInterest().stripTrailingZeros());
|
||||
// Total outstanding interest amount on the current period, if the current period due date is after than the
|
||||
// current date
|
||||
@@ -6131,21 +6131,21 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().get(index);
|
||||
assertEquals(dueDate, period.getDueDate());
|
||||
assertEquals(paidDate, period.getObligationsMetOnDate());
|
||||
assertEquals(balanceOfLoan, period.getPrincipalLoanBalanceOutstanding());
|
||||
assertEquals(principalDue, period.getPrincipalDue());
|
||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||
assertEquals(feeDue, period.getFeeChargesDue());
|
||||
assertEquals(feePaid, period.getFeeChargesPaid());
|
||||
assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyDue, period.getPenaltyChargesDue());
|
||||
assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
|
||||
assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding());
|
||||
assertEquals(interestDue, period.getInterestDue());
|
||||
assertEquals(interestPaid, period.getInterestPaid());
|
||||
assertEquals(interestOutstanding, period.getInterestOutstanding());
|
||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||
assertEquals(balanceOfLoan, Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding()));
|
||||
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
|
||||
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
|
||||
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
|
||||
assertEquals(feeDue, Utils.getDoubleValue(period.getFeeChargesDue()));
|
||||
assertEquals(feePaid, Utils.getDoubleValue(period.getFeeChargesPaid()));
|
||||
assertEquals(feeOutstanding, Utils.getDoubleValue(period.getFeeChargesOutstanding()));
|
||||
assertEquals(penaltyDue, Utils.getDoubleValue(period.getPenaltyChargesDue()));
|
||||
assertEquals(penaltyPaid, Utils.getDoubleValue(period.getPenaltyChargesPaid()));
|
||||
assertEquals(penaltyOutstanding, Utils.getDoubleValue(period.getPenaltyChargesOutstanding()));
|
||||
assertEquals(interestDue, Utils.getDoubleValue(period.getInterestDue()));
|
||||
assertEquals(interestPaid, Utils.getDoubleValue(period.getInterestPaid()));
|
||||
assertEquals(interestOutstanding, Utils.getDoubleValue(period.getInterestOutstanding()));
|
||||
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
|
||||
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
|
||||
}
|
||||
|
||||
private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal,
|
||||
@@ -6181,18 +6181,18 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
|
||||
private static void validateLoanTransaction(GetLoansLoanIdResponse loanDetails, int index, double transactionAmount,
|
||||
double principalPortion, double overPaidPortion, double loanBalance) {
|
||||
assertEquals(transactionAmount, loanDetails.getTransactions().get(index).getAmount());
|
||||
assertEquals(principalPortion, loanDetails.getTransactions().get(index).getPrincipalPortion());
|
||||
assertEquals(overPaidPortion, loanDetails.getTransactions().get(index).getOverpaymentPortion());
|
||||
assertEquals(loanBalance, loanDetails.getTransactions().get(index).getOutstandingLoanBalance());
|
||||
assertEquals(transactionAmount, Utils.getDoubleValue(loanDetails.getTransactions().get(index).getAmount()));
|
||||
assertEquals(principalPortion, Utils.getDoubleValue(loanDetails.getTransactions().get(index).getPrincipalPortion()));
|
||||
assertEquals(overPaidPortion, Utils.getDoubleValue(loanDetails.getTransactions().get(index).getOverpaymentPortion()));
|
||||
assertEquals(loanBalance, Utils.getDoubleValue(loanDetails.getTransactions().get(index).getOutstandingLoanBalance()));
|
||||
}
|
||||
|
||||
private void validateLoanCharge(GetLoansLoanIdResponse loanDetails, int index, LocalDate dueDate, double charged, double paid,
|
||||
double outstanding) {
|
||||
GetLoansLoanIdLoanChargeData chargeData = loanDetails.getCharges().get(index);
|
||||
assertEquals(dueDate, chargeData.getDueDate());
|
||||
assertEquals(charged, chargeData.getAmount());
|
||||
assertEquals(paid, chargeData.getAmountPaid());
|
||||
assertEquals(outstanding, chargeData.getAmountOutstanding());
|
||||
assertEquals(charged, Utils.getDoubleValue(chargeData.getAmount()));
|
||||
assertEquals(paid, Utils.getDoubleValue(chargeData.getAmountPaid()));
|
||||
assertEquals(outstanding, Utils.getDoubleValue(chargeData.getAmountOutstanding()));
|
||||
}
|
||||
}
|
||||
|
||||
+64
-54
@@ -177,22 +177,22 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||
assertEquals(dueDate, period.getDueDate());
|
||||
assertEquals(principalDue, period.getPrincipalDue());
|
||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
|
||||
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
|
||||
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
|
||||
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
|
||||
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
|
||||
}
|
||||
|
||||
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, double principalDue,
|
||||
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||
assertEquals(principalDue, period.getPrincipalDue());
|
||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
|
||||
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
|
||||
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
|
||||
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
|
||||
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
|
||||
}
|
||||
|
||||
protected static void validateFullyUnpaidRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, String dueDate,
|
||||
@@ -233,20 +233,20 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
GetLoansLoanIdRepaymentPeriod period = loanDetails.getRepaymentSchedule().getPeriods().stream()
|
||||
.filter(p -> Objects.equals(p.getPeriod(), index)).findFirst().orElseThrow();
|
||||
assertEquals(dueDate, period.getDueDate());
|
||||
assertEquals(principalDue, period.getPrincipalDue());
|
||||
assertEquals(principalPaid, period.getPrincipalPaid());
|
||||
assertEquals(principalOutstanding, period.getPrincipalOutstanding());
|
||||
assertEquals(feeDue, period.getFeeChargesDue());
|
||||
assertEquals(feePaid, period.getFeeChargesPaid());
|
||||
assertEquals(feeOutstanding, period.getFeeChargesOutstanding());
|
||||
assertEquals(penaltyDue, period.getPenaltyChargesDue());
|
||||
assertEquals(penaltyPaid, period.getPenaltyChargesPaid());
|
||||
assertEquals(penaltyOutstanding, period.getPenaltyChargesOutstanding());
|
||||
assertEquals(interestDue, period.getInterestDue());
|
||||
assertEquals(interestPaid, period.getInterestPaid());
|
||||
assertEquals(interestOutstanding, period.getInterestOutstanding());
|
||||
assertEquals(paidInAdvance, period.getTotalPaidInAdvanceForPeriod());
|
||||
assertEquals(paidLate, period.getTotalPaidLateForPeriod());
|
||||
assertEquals(principalDue, Utils.getDoubleValue(period.getPrincipalDue()));
|
||||
assertEquals(principalPaid, Utils.getDoubleValue(period.getPrincipalPaid()));
|
||||
assertEquals(principalOutstanding, Utils.getDoubleValue(period.getPrincipalOutstanding()));
|
||||
assertEquals(feeDue, Utils.getDoubleValue(period.getFeeChargesDue()));
|
||||
assertEquals(feePaid, Utils.getDoubleValue(period.getFeeChargesPaid()));
|
||||
assertEquals(feeOutstanding, Utils.getDoubleValue(period.getFeeChargesOutstanding()));
|
||||
assertEquals(penaltyDue, Utils.getDoubleValue(period.getPenaltyChargesDue()));
|
||||
assertEquals(penaltyPaid, Utils.getDoubleValue(period.getPenaltyChargesPaid()));
|
||||
assertEquals(penaltyOutstanding, Utils.getDoubleValue(period.getPenaltyChargesOutstanding()));
|
||||
assertEquals(interestDue, Utils.getDoubleValue(period.getInterestDue()));
|
||||
assertEquals(interestPaid, Utils.getDoubleValue(period.getInterestPaid()));
|
||||
assertEquals(interestOutstanding, Utils.getDoubleValue(period.getInterestOutstanding()));
|
||||
assertEquals(paidInAdvance, Utils.getDoubleValue(period.getTotalPaidInAdvanceForPeriod()));
|
||||
assertEquals(paidLate, Utils.getDoubleValue(period.getTotalPaidLateForPeriod()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -410,6 +410,15 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
.loanScheduleProcessingType("HORIZONTAL");//
|
||||
}
|
||||
|
||||
protected PostLoanProductsRequest create4IProgressiveWithCapitalizedIncome() {
|
||||
return create4IProgressive().enableIncomeCapitalization(true)//
|
||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)//
|
||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)//
|
||||
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())//
|
||||
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())//
|
||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE);
|
||||
}
|
||||
|
||||
// Loan product with proper accounting setup
|
||||
protected PostLoanProductsRequest createOnePeriod30DaysPeriodicAccrualProduct(double interestRatePerPeriod) {
|
||||
return new PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6))//
|
||||
@@ -615,7 +624,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size());
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
Optional<GetLoansLoanIdTransactions> optTx = loanDetails.getTransactions().stream()
|
||||
.filter(item -> Objects.equals(item.getAmount(), tr.amount) //
|
||||
.filter(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)))
|
||||
.findFirst();
|
||||
@@ -638,16 +647,17 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
} else {
|
||||
Assertions.assertEquals(transactions.length, loanDetails.getTransactions().size(), "Number of transactions on loan " + loanId);
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
boolean found = loanDetails.getTransactions().stream().anyMatch(item -> Objects.equals(item.getAmount(), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) //
|
||||
&& Objects.equals(item.getOutstandingLoanBalance(), tr.outstandingPrincipal) //
|
||||
&& Objects.equals(item.getPrincipalPortion(), tr.principalPortion) //
|
||||
&& Objects.equals(item.getInterestPortion(), tr.interestPortion) //
|
||||
&& Objects.equals(item.getFeeChargesPortion(), tr.feePortion) //
|
||||
&& Objects.equals(item.getPenaltyChargesPortion(), tr.penaltyPortion) //
|
||||
&& Objects.equals(item.getOverpaymentPortion(), tr.overpaymentPortion) //
|
||||
&& Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) //
|
||||
boolean found = loanDetails.getTransactions().stream()
|
||||
.anyMatch(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOutstandingLoanBalance()), tr.outstandingPrincipal) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPrincipalPortion()), tr.principalPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getInterestPortion()), tr.interestPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getFeeChargesPortion()), tr.feePortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPenaltyChargesPortion()), tr.penaltyPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOverpaymentPortion()), tr.overpaymentPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()), tr.unrecognizedPortion) //
|
||||
);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanId);
|
||||
});
|
||||
@@ -823,8 +833,8 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
private void verifyPeriodsEquality(List<GetLoansLoanIdRepaymentPeriod> savedPeriods, List<GetLoansLoanIdRepaymentPeriod> actualPeriods,
|
||||
int startIndex, int endIndex, boolean shouldEqual) {
|
||||
for (int i = startIndex; i < endIndex; i++) {
|
||||
Double savedTotalDue = savedPeriods.get(i).getTotalDueForPeriod();
|
||||
Double actualTotalDue = actualPeriods.get(i).getTotalDueForPeriod();
|
||||
Double savedTotalDue = Utils.getDoubleValue(savedPeriods.get(i).getTotalDueForPeriod());
|
||||
Double actualTotalDue = Utils.getDoubleValue(actualPeriods.get(i).getTotalDueForPeriod());
|
||||
|
||||
if (shouldEqual) {
|
||||
assertEquals(savedTotalDue, actualTotalDue, String.format(
|
||||
@@ -848,19 +858,19 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
int installmentNumber = 0;
|
||||
for (int i = 0; i < installments.length; i++) {
|
||||
GetLoansLoanIdRepaymentPeriod period = loanResponse.getRepaymentSchedule().getPeriods().get(i);
|
||||
Double principalDue = period.getPrincipalDue();
|
||||
Double principalDue = Utils.getDoubleValue(period.getPrincipalDue());
|
||||
Double amount = installments[i].principalAmount;
|
||||
|
||||
if (installments[i].completed == null) { // this is for the disbursement
|
||||
Assertions.assertEquals(amount, period.getPrincipalLoanBalanceOutstanding(),
|
||||
Assertions.assertEquals(amount, Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding()),
|
||||
"%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount,
|
||||
period.getPrincipalLoanBalanceOutstanding()));
|
||||
Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding())));
|
||||
} else {
|
||||
Assertions.assertEquals(amount, principalDue,
|
||||
"%d. installment's principal due is different, expected: %.2f, actual: %.2f".formatted(i, amount, principalDue));
|
||||
|
||||
Double interestAmount = installments[i].interestAmount;
|
||||
Double interestDue = period.getInterestDue();
|
||||
Double interestDue = Utils.getDoubleValue(period.getInterestDue());
|
||||
if (interestAmount != null) {
|
||||
Assertions.assertEquals(interestAmount, interestDue,
|
||||
"%d. installment's interest due is different, expected: %.2f, actual: %.2f".formatted(i, interestAmount,
|
||||
@@ -868,14 +878,14 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
Double feeAmount = installments[i].feeAmount;
|
||||
Double feeDue = period.getFeeChargesDue();
|
||||
Double feeDue = Utils.getDoubleValue(period.getFeeChargesDue());
|
||||
if (feeAmount != null) {
|
||||
Assertions.assertEquals(feeAmount, feeDue,
|
||||
"%d. installment's fee charges due is different, expected: %.2f, actual: %.2f".formatted(i, feeAmount, feeDue));
|
||||
}
|
||||
|
||||
Double penaltyAmount = installments[i].penaltyAmount;
|
||||
Double penaltyDue = period.getPenaltyChargesDue();
|
||||
Double penaltyDue = Utils.getDoubleValue(period.getPenaltyChargesDue());
|
||||
if (penaltyAmount != null) {
|
||||
Assertions.assertEquals(penaltyAmount, penaltyDue,
|
||||
"%d. installment's penalty charges due is different, expected: %.2f, actual: %.2f".formatted(i, penaltyAmount,
|
||||
@@ -883,7 +893,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
Double outstandingAmount = installments[i].totalOutstandingAmount;
|
||||
Double totalOutstanding = period.getTotalOutstandingForPeriod();
|
||||
Double totalOutstanding = Utils.getDoubleValue(period.getTotalOutstandingForPeriod());
|
||||
if (outstandingAmount != null) {
|
||||
Assertions.assertEquals(outstandingAmount, totalOutstanding,
|
||||
"%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i, outstandingAmount,
|
||||
@@ -893,7 +903,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
Double outstandingPrincipalExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.principalOutstanding
|
||||
: null;
|
||||
Double outstandingPrincipal = period.getPrincipalOutstanding();
|
||||
Double outstandingPrincipal = Utils.getDoubleValue(period.getPrincipalOutstanding());
|
||||
if (outstandingPrincipalExpected != null) {
|
||||
Assertions.assertEquals(outstandingPrincipalExpected, outstandingPrincipal,
|
||||
"%d. installment's outstanding principal is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
@@ -903,7 +913,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
Double outstandingFeeExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.feeOutstanding
|
||||
: null;
|
||||
Double outstandingFee = period.getFeeChargesOutstanding();
|
||||
Double outstandingFee = Utils.getDoubleValue(period.getFeeChargesOutstanding());
|
||||
if (outstandingFeeExpected != null) {
|
||||
Assertions.assertEquals(outstandingFeeExpected, outstandingFee,
|
||||
"%d. installment's outstanding fee is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
@@ -913,7 +923,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
Double outstandingPenaltyExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.penaltyOutstanding
|
||||
: null;
|
||||
Double outstandingPenalty = period.getPenaltyChargesOutstanding();
|
||||
Double outstandingPenalty = Utils.getDoubleValue(period.getPenaltyChargesOutstanding());
|
||||
if (outstandingPenaltyExpected != null) {
|
||||
Assertions.assertEquals(outstandingPenaltyExpected, outstandingPenalty,
|
||||
"%d. installment's outstanding penalty is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
@@ -923,7 +933,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
Double outstandingTotalExpected = installments[i].outstandingAmounts != null
|
||||
? installments[i].outstandingAmounts.totalOutstanding
|
||||
: null;
|
||||
Double outstandingTotal = period.getTotalOutstandingForPeriod();
|
||||
Double outstandingTotal = Utils.getDoubleValue(period.getTotalOutstandingForPeriod());
|
||||
if (outstandingTotalExpected != null) {
|
||||
Assertions.assertEquals(outstandingTotalExpected, outstandingTotal,
|
||||
"%d. installment's total outstanding is different, expected: %.2f, actual: %.2f".formatted(i,
|
||||
@@ -931,7 +941,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
}
|
||||
|
||||
Double loanBalanceExpected = installments[i].loanBalance;
|
||||
Double loanBalance = period.getPrincipalLoanBalanceOutstanding();
|
||||
Double loanBalance = Utils.getDoubleValue(period.getPrincipalLoanBalanceOutstanding());
|
||||
if (loanBalanceExpected != null) {
|
||||
Assertions.assertEquals(loanBalanceExpected, loanBalance,
|
||||
"%d. installment's loan balance is different, expected: %.2f, actual: %.2f".formatted(i, loanBalanceExpected,
|
||||
@@ -1226,11 +1236,11 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
||||
|
||||
protected void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
|
||||
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
|
||||
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
|
||||
assertEquals(totalRepayment, loanDetails.getSummary().getTotalRepayment());
|
||||
assertEquals(principalOutstanding, loanDetails.getSummary().getPrincipalOutstanding());
|
||||
assertEquals(principalPaid, loanDetails.getSummary().getPrincipalPaid());
|
||||
assertEquals(totalOverpaid, loanDetails.getTotalOverpaid());
|
||||
assertEquals(totalOutstanding, Utils.getDoubleValue(loanDetails.getSummary().getTotalOutstanding()));
|
||||
assertEquals(totalRepayment, Utils.getDoubleValue(loanDetails.getSummary().getTotalRepayment()));
|
||||
assertEquals(principalOutstanding, Utils.getDoubleValue(loanDetails.getSummary().getPrincipalOutstanding()));
|
||||
assertEquals(principalPaid, Utils.getDoubleValue(loanDetails.getSummary().getPrincipalPaid()));
|
||||
assertEquals(totalOverpaid, Utils.getDoubleValue(loanDetails.getTotalOverpaid()));
|
||||
}
|
||||
|
||||
protected void checkMaturityDates(long loanId, LocalDate expectedMaturityDate, LocalDate actualMaturityDate) {
|
||||
|
||||
+3
-1
@@ -38,6 +38,7 @@ import org.apache.fineract.client.models.PutLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -619,7 +620,8 @@ public class InstallmentLevelDelinquencyAPIIntegrationTests extends BaseLoanInte
|
||||
.getInstallmentLevelDelinquency();
|
||||
|
||||
assertThat(loan.getDelinquent().getDelinquentDays()).isEqualTo(loanLevelDelinquentDays);
|
||||
assertThat(loan.getDelinquent().getDelinquentAmount()).isEqualByComparingTo(Double.valueOf(loanLevelDelinquentAmount));
|
||||
assertThat(Utils.getDoubleValue(loan.getDelinquent().getDelinquentAmount()))
|
||||
.isEqualByComparingTo(Double.valueOf(loanLevelDelinquentAmount));
|
||||
|
||||
if (expectedInstallmentLevelDelinquencyData != null && expectedInstallmentLevelDelinquencyData.length > 0) {
|
||||
assertThat(installmentLevelDelinquency).isNotNull();
|
||||
|
||||
+15
-12
@@ -33,6 +33,7 @@ import org.apache.fineract.client.models.PostClientsResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -468,8 +469,9 @@ public class LoanCOBCreateAccrualsTest extends BaseLoanIntegrationTest {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
|
||||
// No unexpected big accruals or any accrual adjustments
|
||||
Assertions.assertTrue(loanDetails.getTransactions().stream().noneMatch(t -> (t.getType().getAccrual() && t.getAmount() > 0.31)
|
||||
|| "loanTransactionType.accrualAdjustment".equals(t.getType().getCode())));
|
||||
Assertions.assertTrue(
|
||||
loanDetails.getTransactions().stream().noneMatch(t -> (t.getType().getAccrual() && t.getAmount().doubleValue() > 0.31)
|
||||
|| "loanTransactionType.accrualAdjustment".equals(t.getType().getCode())));
|
||||
|
||||
// Accruals around installment due dates are as expected
|
||||
validateTransactionsExist(loanDetails, //
|
||||
@@ -691,16 +693,17 @@ public class LoanCOBCreateAccrualsTest extends BaseLoanIntegrationTest {
|
||||
|
||||
private void validateTransactionsExist(GetLoansLoanIdResponse loanDetails, TransactionExt... transactions) {
|
||||
Arrays.stream(transactions).forEach(tr -> {
|
||||
boolean found = loanDetails.getTransactions().stream().anyMatch(item -> Objects.equals(item.getAmount(), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) //
|
||||
&& Objects.equals(item.getOutstandingLoanBalance(), tr.outstandingPrincipal) //
|
||||
&& Objects.equals(item.getPrincipalPortion(), tr.principalPortion) //
|
||||
&& Objects.equals(item.getInterestPortion(), tr.interestPortion) //
|
||||
&& Objects.equals(item.getFeeChargesPortion(), tr.feePortion) //
|
||||
&& Objects.equals(item.getPenaltyChargesPortion(), tr.penaltyPortion) //
|
||||
&& Objects.equals(item.getOverpaymentPortion(), tr.overpaymentPortion) //
|
||||
&& Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) //
|
||||
boolean found = loanDetails.getTransactions().stream()
|
||||
.anyMatch(item -> Objects.equals(Utils.getDoubleValue(item.getAmount()), tr.amount) //
|
||||
&& Objects.equals(item.getType().getValue(), tr.type) //
|
||||
&& Objects.equals(item.getDate(), LocalDate.parse(tr.date, dateTimeFormatter)) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOutstandingLoanBalance()), tr.outstandingPrincipal) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPrincipalPortion()), tr.principalPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getInterestPortion()), tr.interestPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getFeeChargesPortion()), tr.feePortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getPenaltyChargesPortion()), tr.penaltyPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getOverpaymentPortion()), tr.overpaymentPortion) //
|
||||
&& Objects.equals(Utils.getDoubleValue(item.getUnrecognizedIncomePortion()), tr.unrecognizedPortion) //
|
||||
);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr + " on loan " + loanDetails.getId());
|
||||
});
|
||||
|
||||
+3
-3
@@ -78,7 +78,7 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Capitalized Income", "01 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "01 January 2024"), //
|
||||
transaction(0.02, "Accrual", "02 January 2024"), //
|
||||
transaction(0.03, "Accrual", "02 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "02 January 2024") //
|
||||
);
|
||||
|
||||
@@ -89,8 +89,8 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
journalEntry(50, deferredIncomeLiabilityAccount, "CREDIT"), //
|
||||
journalEntry(0.55, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(0.55, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.02, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.02, interestIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.03, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.03, interestIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.55, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(0.55, feeIncomeAccount, "CREDIT") //
|
||||
);
|
||||
|
||||
+4
-3
@@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -78,8 +79,8 @@ public class LoanChargeProgressiveTest extends BaseLoanIntegrationTest {
|
||||
final PostChargesResponse chargeResponse = createCharge(20.0d, "EUR");
|
||||
addLoanCharge(loanId, chargeResponse.getResourceId(), "03 October 2024", 20.0d);
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertTrue(
|
||||
loanDetails.getTransactions().stream().anyMatch(t -> t.getType().getAccrual() && t.getAmount().equals(20.0d)));
|
||||
Assertions.assertTrue(loanDetails.getTransactions().stream()
|
||||
.anyMatch(t -> t.getType().getAccrual() && Utils.getDoubleValue(t.getAmount()).equals(20.0d)));
|
||||
});
|
||||
runAt("04 October 2024", () -> {
|
||||
globalConfigurationHelper.manageConfigurations(GlobalConfigurationConstants.ENABLE_IMMEDIATE_CHARGE_ACCRUAL_POST_MATURITY,
|
||||
@@ -87,7 +88,7 @@ public class LoanChargeProgressiveTest extends BaseLoanIntegrationTest {
|
||||
executeInlineCOB(loanId);
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertTrue(loanDetails.getTransactions().stream()
|
||||
.anyMatch(t -> t.getType().getAccrual() && t.getFeeChargesPortion().equals(20.0d)));
|
||||
.anyMatch(t -> t.getType().getAccrual() && Utils.getDoubleValue(t.getFeeChargesPortion()).equals(20.0d)));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -28,6 +28,7 @@ import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -231,12 +232,14 @@ public class LoanDelinquencyForNonActiveAccountsTest extends BaseLoanIntegration
|
||||
.getInstallmentLevelDelinquency();
|
||||
|
||||
assertThat(loan.getDelinquent().getDelinquentDays()).isEqualTo(loanLevelDelinquentDays);
|
||||
assertThat(loan.getDelinquent().getDelinquentAmount()).isEqualByComparingTo(Double.valueOf(loanLevelDelinquentAmount));
|
||||
assertThat(Utils.getDoubleValue(loan.getDelinquent().getDelinquentAmount()))
|
||||
.isEqualByComparingTo(Double.valueOf(loanLevelDelinquentAmount));
|
||||
if (expectedLastRepaymentDate != null && expectedLastRepaymentAmount != null) {
|
||||
assertThat(loan.getDelinquent().getLastRepaymentDate()).isNotNull();
|
||||
Assertions.assertEquals(expectedLastRepaymentDate, loan.getDelinquent().getLastRepaymentDate().format(dateTimeFormatter));
|
||||
assertThat(loan.getDelinquent().getLastRepaymentAmount()).isNotNull();
|
||||
assertThat(loan.getDelinquent().getLastRepaymentAmount()).isEqualByComparingTo(Double.valueOf(expectedLastRepaymentAmount));
|
||||
assertThat(Utils.getDoubleValue(loan.getDelinquent().getLastRepaymentAmount()))
|
||||
.isEqualByComparingTo(Double.valueOf(expectedLastRepaymentAmount));
|
||||
}
|
||||
|
||||
if (expectedInstallmentLevelDelinquencyData != null && expectedInstallmentLevelDelinquencyData.length > 0) {
|
||||
|
||||
+7
-6
@@ -314,7 +314,7 @@ public class LoanDisbursementDetailsIntegrationTest {
|
||||
getLoansLoanIdResponse = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
|
||||
assertNotNull(getLoansLoanIdResponse);
|
||||
this.loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
|
||||
final Double limit = 2.0;
|
||||
final BigDecimal limit = BigDecimal.TWO;
|
||||
evaluateEqualInstallmentsForRepaymentSchedule(getLoansLoanIdResponse.getRepaymentSchedule(), limit);
|
||||
log.info("-----------MULTI DISBURSAL LOAN EQUAL INSTALLMENTS SUCCESSFULLY-------");
|
||||
}
|
||||
@@ -665,9 +665,9 @@ public class LoanDisbursementDetailsIntegrationTest {
|
||||
return collateral;
|
||||
}
|
||||
|
||||
public void evaluateEqualInstallmentsForRepaymentSchedule(GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule, Double limit) {
|
||||
Double totalOutstandingForPeriod = 0.0;
|
||||
Double totalInstallmentAmountForPeriod = 0.0;
|
||||
public void evaluateEqualInstallmentsForRepaymentSchedule(GetLoansLoanIdRepaymentSchedule getLoanRepaymentSchedule, BigDecimal limit) {
|
||||
BigDecimal totalOutstandingForPeriod = BigDecimal.ZERO;
|
||||
BigDecimal totalInstallmentAmountForPeriod = BigDecimal.ZERO;
|
||||
if (getLoanRepaymentSchedule != null) {
|
||||
log.info("Loan with {} periods", getLoanRepaymentSchedule.getPeriods().size());
|
||||
for (GetLoansLoanIdRepaymentPeriod period : getLoanRepaymentSchedule.getPeriods()) {
|
||||
@@ -678,8 +678,9 @@ public class LoanDisbursementDetailsIntegrationTest {
|
||||
totalOutstandingForPeriod = period.getTotalOutstandingForPeriod();
|
||||
totalInstallmentAmountForPeriod = period.getTotalInstallmentAmountForPeriod();
|
||||
} else {
|
||||
assertTrue(Math.abs(period.getTotalOutstandingForPeriod() - totalOutstandingForPeriod) <= limit);
|
||||
assertTrue(Math.abs(period.getTotalInstallmentAmountForPeriod() - totalInstallmentAmountForPeriod) <= limit);
|
||||
assertTrue(period.getTotalOutstandingForPeriod().subtract(totalOutstandingForPeriod).abs().compareTo(limit) <= 0);
|
||||
assertTrue(period.getTotalInstallmentAmountForPeriod().subtract(totalInstallmentAmountForPeriod).abs()
|
||||
.compareTo(limit) <= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ public class LoanRefundTransactionTest extends BaseLoanIntegrationTest {
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
Assertions.assertTrue(loanDetails.getTransactions().stream()
|
||||
.filter(transaction -> transaction.getType().getCode().equals("loanTransactionType.interestRefund"))
|
||||
.allMatch(transaction -> transaction.getAmount().compareTo(0.0) > 0));
|
||||
.allMatch(transaction -> transaction.getAmount().doubleValue() > 0.0));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+36
-34
@@ -110,9 +110,9 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@@ -157,11 +157,11 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalPaidForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalPaidForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertEquals(expectedRepaymentAmount, summary.getTotalOutstanding());
|
||||
assertEquals(expectedRepaymentAmount, Utils.getDoubleValue(summary.getTotalOutstanding()));
|
||||
assertEquals(expectedDownPaymentAmount, summary.getTotalRepaymentTransaction());
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@@ -205,9 +205,9 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@@ -253,12 +253,12 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedFirstDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedSecondDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
@@ -306,15 +306,15 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
Double expectedTotalRepaymentAmount = expectedFirstDownPaymentAmount + expectedSecondDownPaymentAmount;
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(period.getTotalPaidForPeriod()) //
|
||||
.anyMatch(period -> expectedFirstDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalPaidForPeriod())) //
|
||||
&& expectedFirstDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(period.getTotalPaidForPeriod())
|
||||
.anyMatch(period -> expectedSecondDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalPaidForPeriod()))
|
||||
&& expectedSecondDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
assertEquals(expectedRepaymentAmount, summary.getTotalOutstanding());
|
||||
assertEquals(expectedTotalRepaymentAmount, summary.getTotalRepaymentTransaction());
|
||||
assertEquals(expectedRepaymentAmount, Utils.getDoubleValue(summary.getTotalOutstanding()));
|
||||
assertEquals(expectedTotalRepaymentAmount, Utils.getDoubleValue(summary.getTotalRepaymentTransaction()));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -529,14 +529,14 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate()) //
|
||||
&& Double.valueOf(0.00).equals(period.getFeeChargesDue())));
|
||||
&& Double.valueOf(0.00).equals(Utils.getDoubleValue(period.getFeeChargesDue()))));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedTotalDueForRepaymentInstallment.equals(period.getTotalDueForPeriod()) //
|
||||
&& expectedRepaymentAmount.equals(period.getPrincipalDue()) //
|
||||
.anyMatch(period -> expectedTotalDueForRepaymentInstallment.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getPrincipalDue())) //
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate()) //
|
||||
&& feeAmount.equals(period.getFeeChargesDue())));
|
||||
&& feeAmount.equals(Utils.getDoubleValue(period.getFeeChargesDue()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -694,16 +694,16 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate()) //
|
||||
&& Double.valueOf(0.00).equals(period.getFeeChargesDue()) //
|
||||
&& Double.valueOf(0.00).equals(period.getInterestDue())));
|
||||
&& Double.valueOf(0.00).equals(Utils.getDoubleValue(period.getFeeChargesDue())) //
|
||||
&& Double.valueOf(0.00).equals(Utils.getDoubleValue(period.getInterestDue()))));
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedTotalDueForRepaymentInstallment.equals(period.getTotalDueForPeriod()) //
|
||||
&& expectedRepaymentAmount.equals(period.getPrincipalDue()) //
|
||||
.anyMatch(period -> expectedTotalDueForRepaymentInstallment.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getPrincipalDue())) //
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate()) //
|
||||
&& feeAmount.equals(period.getFeeChargesDue()) //
|
||||
&& Double.valueOf(10.0).equals(period.getInterestDue())));
|
||||
&& feeAmount.equals(Utils.getDoubleValue(period.getFeeChargesDue())) //
|
||||
&& Double.valueOf(10.0).equals(Utils.getDoubleValue(period.getInterestDue()))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -864,10 +864,11 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
LocalDate expectedRepaymentDueDate = LocalDate.of(2022, 10, 3);
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalDueForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalDueForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(
|
||||
periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalDueForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
assertNotNull(loanDetails.getDelinquencyRange());
|
||||
assertEquals(2, loanDetails.getDelinquent().getDelinquentDays());
|
||||
} finally {
|
||||
@@ -1662,12 +1663,13 @@ public class LoanRepaymentScheduleWithDownPaymentTest extends BaseLoanIntegratio
|
||||
Double expectedTotalRepaymentTransactionAmount = 0.00;
|
||||
|
||||
assertTrue(periods.stream() //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(period.getTotalOutstandingForPeriod()) //
|
||||
.anyMatch(period -> expectedDownPaymentAmount.equals(Utils.getDoubleValue(period.getTotalOutstandingForPeriod())) //
|
||||
&& expectedDownPaymentDueDate.equals(period.getDueDate())));
|
||||
assertEquals(expectedTotalOutstandingAmount, summary.getTotalOutstanding());
|
||||
assertEquals(expectedTotalOutstandingAmount, Utils.getDoubleValue(summary.getTotalOutstanding()));
|
||||
assertEquals(expectedTotalRepaymentTransactionAmount, summary.getTotalRepaymentTransaction());
|
||||
assertTrue(periods.stream().anyMatch(period -> expectedRepaymentAmount.equals(period.getTotalOutstandingForPeriod())
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
assertTrue(periods.stream()
|
||||
.anyMatch(period -> expectedRepaymentAmount.equals(Utils.getDoubleValue(period.getTotalOutstandingForPeriod()))
|
||||
&& expectedRepaymentDueDate.equals(period.getDueDate())));
|
||||
}
|
||||
|
||||
private void checkNoDownPaymentTransaction(final Integer loanID) {
|
||||
|
||||
+36
@@ -19,7 +19,9 @@
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
@@ -248,4 +250,38 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCapitalizedIncomeExistsInRepaymentScheduleAndModifiesPrincipal() {
|
||||
runAt("01 March 2023", () -> {
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||
.createLoanProduct(create4IProgressiveWithCapitalizedIncome());
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
|
||||
loanProductsResponse.getResourceId(), "01 March 2023", 10000.00, 12.00, 4, null));
|
||||
loanId = postLoansResponse.getLoanId();
|
||||
loanTransactionHelper.approveLoan(loanId, approveLoanRequest(10000.00, "01 March 2023"));
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 March 2023");
|
||||
});
|
||||
runAt("02 March 2023", () -> {
|
||||
loanTransactionHelper.addCapitalizedIncome(loanId, "02 March 2023", 100.00);
|
||||
});
|
||||
|
||||
BigDecimal thousand = BigDecimal.valueOf(1000.0);
|
||||
BigDecimal hundred = BigDecimal.valueOf(100.0);
|
||||
BigDecimal thousandOneHundred = BigDecimal.valueOf(1100.0);
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
|
||||
Assertions.assertEquals(thousand, loanDetails.getPrincipal().setScale(1, RoundingMode.HALF_UP));
|
||||
Assertions.assertEquals(thousand, loanDetails.getSummary().getPrincipalDisbursed().setScale(1, RoundingMode.HALF_UP));
|
||||
Assertions.assertEquals(hundred,
|
||||
loanDetails.getSummary().getTotalPrincipalFromCapitalizedIncome().setScale(1, RoundingMode.HALF_UP));
|
||||
Assertions.assertEquals(thousandOneHundred, loanDetails.getSummary().getTotalPrincipal().setScale(1, RoundingMode.HALF_UP));
|
||||
Assertions.assertEquals(thousandOneHundred, loanDetails.getSummary().getPrincipalOutstanding().setScale(1, RoundingMode.HALF_UP));
|
||||
|
||||
List<GetLoansLoanIdRepaymentPeriod> periods = loanDetails.getRepaymentSchedule().getPeriods();
|
||||
Assertions.assertEquals(6, periods.size());
|
||||
Assertions.assertEquals(thousand, periods.get(0).getPrincipalLoanBalanceOutstanding().setScale(1, RoundingMode.HALF_UP));
|
||||
Assertions.assertEquals(hundred, periods.get(1).getPrincipalLoanBalanceOutstanding().setScale(1, RoundingMode.HALF_UP));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
-2
@@ -46,8 +46,8 @@ public class ProgressiveLoanTrancheTest extends BaseLoanIntegrationTest {
|
||||
|
||||
runAt("20 December 2024", () -> {
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
|
||||
500.0, 7.0, 6, (request) -> request.disbursementData(
|
||||
List.of(new PostLoansDisbursementData().expectedDisbursementDate("20 December 2024").principal(100.0))));
|
||||
500.0, 7.0, 6, (request) -> request.disbursementData(List.of(new PostLoansDisbursementData()
|
||||
.expectedDisbursementDate("20 December 2024").principal(BigDecimal.valueOf(100.0)))));
|
||||
|
||||
loanIdRef.set(loanId);
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ import io.restassured.response.Response;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.io.File;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.security.SecureRandom;
|
||||
import java.text.DateFormat;
|
||||
import java.text.ParseException;
|
||||
@@ -569,4 +571,8 @@ public final class Utils {
|
||||
public static long getDifferenceInMonths(final LocalDate localDateBefore, final LocalDate localDateAfter) {
|
||||
return MONTHS.between(localDateBefore, localDateAfter);
|
||||
}
|
||||
|
||||
public static Double getDoubleValue(BigDecimal amount) {
|
||||
return amount == null ? null : amount.setScale(2, RoundingMode.HALF_UP).doubleValue();
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -2688,7 +2688,7 @@ public class LoanTransactionHelper {
|
||||
log.info(" Id {} code {} date {} amount {}", transaction.getId(), transaction.getType().getCode(), transaction.getDate(),
|
||||
transaction.getAmount());
|
||||
if (transactionType.equals(transaction.getType().getCode())) {
|
||||
transactionsAmount += transaction.getAmount();
|
||||
transactionsAmount += Utils.getDoubleValue(transaction.getAmount());
|
||||
}
|
||||
}
|
||||
assertEquals(amountExpected, transactionsAmount);
|
||||
@@ -2707,7 +2707,7 @@ public class LoanTransactionHelper {
|
||||
}
|
||||
}
|
||||
assertEquals(transactionExpected, Utils.dateFormatter.format(lastTransaction.getDate()));
|
||||
assertEquals(amountExpected, lastTransaction.getAmount());
|
||||
assertEquals(amountExpected, Utils.getDoubleValue(lastTransaction.getAmount()));
|
||||
return lastTransaction.getId();
|
||||
}
|
||||
|
||||
@@ -2722,7 +2722,7 @@ public class LoanTransactionHelper {
|
||||
if (getLoansLoanIdSummary != null) {
|
||||
log.info("Loan with Principal Outstanding Balance {} expected {}", getLoansLoanIdSummary.getPrincipalOutstanding(),
|
||||
amountExpected);
|
||||
assertEquals(amountExpected, getLoansLoanIdSummary.getPrincipalOutstanding());
|
||||
assertEquals(amountExpected, Utils.getDoubleValue(getLoansLoanIdSummary.getPrincipalOutstanding()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2730,7 +2730,7 @@ public class LoanTransactionHelper {
|
||||
GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
|
||||
if (getLoansLoanIdSummary != null) {
|
||||
log.info("Loan with Fees Outstanding Balance {} expected {}", getLoansLoanIdSummary.getFeeChargesOutstanding(), amountExpected);
|
||||
assertEquals(amountExpected, getLoansLoanIdSummary.getFeeChargesOutstanding());
|
||||
assertEquals(amountExpected, Utils.getDoubleValue(getLoansLoanIdSummary.getFeeChargesOutstanding()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2738,14 +2738,14 @@ public class LoanTransactionHelper {
|
||||
GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
|
||||
assertNotNull(getLoansLoanIdSummary);
|
||||
log.info("Loan with Fees Outstanding Balance {} expected {}", getLoansLoanIdSummary.getFeeChargesOutstanding(), amountExpected);
|
||||
assertEquals(amountExpected, getLoansLoanIdSummary.getPenaltyChargesOutstanding());
|
||||
assertEquals(amountExpected, Utils.getDoubleValue(getLoansLoanIdSummary.getPenaltyChargesOutstanding()));
|
||||
}
|
||||
|
||||
public void validateLoanTotalOustandingBalance(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) {
|
||||
GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
|
||||
if (getLoansLoanIdSummary != null) {
|
||||
log.info("Loan with Total Outstanding Balance {} expected {}", getLoansLoanIdSummary.getTotalOutstanding(), amountExpected);
|
||||
assertEquals(amountExpected, getLoansLoanIdSummary.getTotalOutstanding());
|
||||
assertEquals(amountExpected, Utils.getDoubleValue(getLoansLoanIdSummary.getTotalOutstanding()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user