diff --git a/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java index 80849842f..14b194f20 100644 --- a/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java +++ b/custom/acme/note/service/src/main/java/com/acme/fineract/portfolio/note/service/AcmeNoteWritePlatformService.java @@ -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 diff --git a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java index ac6320aff..bdf4957a8 100644 --- a/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java +++ b/fineract-core/src/main/java/org/apache/fineract/infrastructure/core/service/DateUtils.java @@ -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); } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java index 7ec9177ce..1d8afbd1f 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformService.java @@ -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); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java index 6f7f7397f..ca35080a0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/EventCheckHelper.java @@ -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 transactionEventCheck( @@ -325,7 +325,7 @@ public class EventCheckHelper { EventAssertion.EventAssertionBuilder 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 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(); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java index 1211bae2a..efb2db2ef 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanChargeAdjustmentStepDef.java @@ -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); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java index f69c0dfe4..0c0c2ebfe 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanDelinquencyStepDef.java @@ -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)// diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java index 6530aede2..0a48f056a 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRepaymentStepDef.java @@ -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()) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 8659a7b64..18e49028f 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -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 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 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 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 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 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())); diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature index 4fb32bd2a..42a33c761 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature @@ -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 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java index fdcd5b02d..32c175deb 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/DisbursementData.java @@ -30,7 +30,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul */ @RequiredArgsConstructor @Getter -public final class DisbursementData implements Comparable { +public final class DisbursementData implements LoanPrincipalRelatedDataHolder, Comparable { private final Long id; private final LocalDate expectedDisbursementDate; @@ -102,8 +102,7 @@ public final class DisbursementData implements Comparable { 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() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPrincipalRelatedDataHolder.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPrincipalRelatedDataHolder.java new file mode 100644 index 000000000..073c192ce --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanPrincipalRelatedDataHolder.java @@ -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 {} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java index b1e147a5d..a359b2749 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSummaryData.java @@ -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; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java index 9dcc97934..0b76608c4 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java @@ -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 repaymentScheduleInstallments, Set charges) { + final List repaymentScheduleInstallments, Set charges, + List 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(); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepaymentPeriodData.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepaymentPeriodData.java new file mode 100644 index 000000000..f5c734841 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepaymentPeriodData.java @@ -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; + } +} diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java index 25d9f030c..79d09ae0c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanRefundValidator.java @@ -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); diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java index cb69a73eb..03c5e7066 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java @@ -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 capitalizedIncomeTransactions = loan.getLoanTransactions(LoanTransaction::isCapitalizedIncome); + loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges(), capitalizedIncomeTransactions); loan.updateLoanOutstandingBalances(); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index c7cd77ae7..595316f42 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -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."; diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java index cdff9421b..ed2d7176a 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformService.java @@ -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, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType); + Collection disbursementData, Collection capitalizedIncomeData, + boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType); Collection retrieveLoanTransactions(Long loanId); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index d4cbfcd79..cfbbe223a 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -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 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 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 installments = transactionCtx.getInstallments(); + List 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 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 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) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanCapitalizedIncomeBalanceRepository.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanCapitalizedIncomeBalanceRepository.java index b1ac6238c..f0185b4e4 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanCapitalizedIncomeBalanceRepository.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanCapitalizedIncomeBalanceRepository.java @@ -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, JpaSpecificationExecutor { + 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 findAllByLoanId(Long loanId); LoanCapitalizedIncomeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId); + + @Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lcib.loan.id = :loanId") + List findRepaymentPeriodDataByLoanId(Long loanId); } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java index 5c09f015f..b44138853 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java @@ -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 diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java index cf0ef76b2..8a7282b62 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java @@ -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 diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 44aa869d2..0991245b5 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -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. diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 0a5d5d991..c1ebc18ed 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -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 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) -> { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java index 675cc6b37..1f0768a1f 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/EmiChangeOperation.java @@ -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; diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java index 436a9d133..cafacef68 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/InterestPeriod.java @@ -62,6 +62,8 @@ public class InterestPeriod implements Comparable { 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 { 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 { 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 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 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 { * 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() { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java index beb3a126d..38cde0ba2 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java @@ -175,9 +175,10 @@ public class ProgressiveLoanInterestScheduleModel { } public Optional 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 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); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java index 2564dc1b9..6382e361c 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java @@ -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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java index a061ce97d..ec811858a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/bulkimport/importhandler/loan/LoanImportHandler.java @@ -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()) // diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 295142664..28fcb876e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -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 = this.loanReadPlatformService.retrieveLoanDisbursementDetails(i.getId()); + List 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 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) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index c3d053d07..98ad1fcfe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -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 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 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 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 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") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSchedulePeriodDataWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSchedulePeriodDataWrapper.java new file mode 100644 index 000000000..760161797 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanSchedulePeriodDataWrapper.java @@ -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; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java index bc8a9ef15..1879abf00 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CommonLoanSummaryDataProvider.java @@ -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()) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java index 040ff7f52..b5e3bea47 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java @@ -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 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 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 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 diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index ca2154bda..dbde91dd9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -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 = retrieveLoanDisbursementDetails(accountData.getId()); + List 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, - boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) { + Collection 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; + private final Collection capitalizedIncomeData; private final LoanScheduleType loanScheduleType; private LocalDate lastDueDate; private BigDecimal outstandingLoanPrincipalBalance; private boolean excludePastUnDisbursed; LoanScheduleResultSetExtractor(final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedLoanData, - Collection disbursementData, boolean isInterestRecalculationEnabled, LoanScheduleType loanScheduleType) { + Collection disbursementData, Collection 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 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, - LocalDate fromDate, LocalDate dueDate, Set disbursementPeriodIds, BigDecimal disbursementChargeAmount, - BigDecimal waivedChargeAmount, List periods) { - BigDecimal disbursedAmount = BigDecimal.ZERO; + private List collectEligibleDisbursementData(LoanScheduleType loanScheduleType, + Collection disbursementData, LocalDate fromDate, LocalDate dueDate, Set disbursementPeriodIds) { + List 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 collectEligibleCapitalizedIncomeData(LocalDate fromDate, LocalDate dueDate, + Set disbursementPeriodIds) { + List 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 periods, List 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 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 { @@ -2168,7 +2251,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT); final Collection 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 { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java index 9b00b712a..dabf4eae2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java @@ -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) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java index 5d50d8197..44500747e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/note/service/NoteWritePlatformServiceJpaRepositoryImpl.java @@ -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); } diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 61fb03607..af37175c5 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -196,4 +196,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0178_add_principal_from_capitalized_income_to_loan_summary.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0178_add_principal_from_capitalized_income_to_loan_summary.xml new file mode 100644 index 000000000..626d536bb --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0178_add_principal_from_capitalized_income_to_loan_summary.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 8723263d3..d7334b44a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -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())); } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 2721be937..8659659b8 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -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 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 savedPeriods, List 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) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java index 493afedb2..21d53e7c9 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/InstallmentLevelDelinquencyAPIIntegrationTests.java @@ -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(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java index b54b02a2d..fc81ebf76 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCOBCreateAccrualsTest.java @@ -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()); }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java index be63821c0..8d96e0b5e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java @@ -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") // ); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java index f84fdbcf3..f020074b4 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeProgressiveTest.java @@ -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))); }); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyForNonActiveAccountsTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyForNonActiveAccountsTest.java index bdecd098f..cf7425440 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyForNonActiveAccountsTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDelinquencyForNonActiveAccountsTest.java @@ -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) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java index 6ce5f9937..996f31da2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDisbursementDetailsIntegrationTest.java @@ -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); } } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java index 6b97c0144..c56458dce 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRefundTransactionTest.java @@ -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)); }); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java index a0cc2f162..579f0847d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRepaymentScheduleWithDownPaymentTest.java @@ -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) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSummaryTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSummaryTest.java index 19f0e11d0..e9b5b6133 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSummaryTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanSummaryTest.java @@ -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 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)); + } + } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTrancheTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTrancheTest.java index 3ea51ae45..0144722d5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTrancheTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTrancheTest.java @@ -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); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java index 8b764cc2c..4472a0fc1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/Utils.java @@ -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(); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java index 5de3e0244..cbc37afd5 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java @@ -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())); } }