From a987956cbc5ceb3e26752379901bb99decab6d0a Mon Sep 17 00:00:00 2001 From: Jose Alberto Hernandez Date: Fri, 9 Dec 2022 09:03:09 -0600 Subject: [PATCH] FINERACT-1785: Allow to add charge (penalty) with same disbursement date --- .../loanaccount/domain/LoanCharge.java | 11 --- ...oanRepaymentScheduleProcessingWrapper.java | 6 +- ...ntScheduleTransactionProcessorFactory.java | 2 - .../LoanChargeSpecificDueDateTest.java | 71 +++++++++++++++++-- .../common/loans/LoanTransactionHelper.java | 7 ++ 5 files changed, 76 insertions(+), 21 deletions(-) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index 3c5ddf8ae..3ab6dbb79 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -632,17 +632,6 @@ public class LoanCharge extends AbstractPersistableCustom { return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive); } - public boolean isDueForCollectionFromIncludingAndUpToAndIncluding(final LocalDate fromNotInclusive, final LocalDate upToAndInclusive) { - final LocalDate dueDate = getDueLocalDate(); - return occursOnDayFromAndIncludingAndUpToAndIncluding(fromNotInclusive, upToAndInclusive, dueDate); - } - - private boolean occursOnDayFromAndIncludingAndUpToAndIncluding(final LocalDate fromAndInclusive, final LocalDate upToAndInclusive, - final LocalDate target) { - return target != null && (target.isEqual(fromAndInclusive) || target.isAfter(fromAndInclusive)) - && !target.isAfter(upToAndInclusive); - } - public boolean isFeeCharge() { return !this.penaltyCharge; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java index 72fec14af..fb7f80f20 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleProcessingWrapper.java @@ -119,8 +119,7 @@ public class LoanRepaymentScheduleProcessingWrapper { } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { cumulative = cumulative.plus(loanCharge.amount()); // Special case for Loan Charges (Due Date) added the same disbursement date - } else if (period.isFirstPeriod() - && loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)) { + } else if (period.isFirstPeriod() && periodStart.equals(loanCharge.getDueDate())) { cumulative = cumulative.plus(loanCharge.amount()); } } @@ -213,6 +212,9 @@ public class LoanRepaymentScheduleProcessingWrapper { cumulative = cumulative.plus(loanChargeAmt); } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { cumulative = cumulative.plus(loanCharge.amount()); + // Special case for Loan Charges (Due Date) added the same disbursement date + } else if (period.isFirstPeriod() && periodStart.equals(loanCharge.getDueDate())) { + cumulative = cumulative.plus(loanCharge.amount()); } } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java index 08afe83fb..06d7be483 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleTransactionProcessorFactory.java @@ -21,13 +21,11 @@ package org.apache.fineract.portfolio.loanaccount.domain; import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.springframework.beans.factory.annotation.Value; -@Slf4j @RequiredArgsConstructor public class LoanRepaymentScheduleTransactionProcessorFactory { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java index 571dd834d..7f0dcbeca 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeSpecificDueDateTest.java @@ -62,7 +62,7 @@ public class LoanChargeSpecificDueDateTest { } @Test - public void testApplyLoanSpecificDueDateChargeWithDisbursementDate() { + public void testApplyLoanSpecificDueDateFeeWithDisbursementDate() { final LocalDate todaysDate = Utils.getLocalDateOfTenant(); @@ -82,7 +82,7 @@ public class LoanChargeSpecificDueDateTest { // Get loan details GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("0.00")); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("0.00"), false); // Apply Loan Charge with specific due date @@ -99,7 +99,7 @@ public class LoanChargeSpecificDueDateTest { // Get loan details expecting to have a delinquency classification getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); - validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("10.00")); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("10.00"), false); // Make a full repayment to close the Loan Float amount = Float.valueOf("1010.00"); @@ -110,7 +110,62 @@ public class LoanChargeSpecificDueDateTest { getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); assertNotNull(getLoansLoanIdResponse); - validateLoanAccount(getLoansLoanIdResponse, Double.valueOf("0.00"), Double.valueOf("0.00")); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf("0.00"), Double.valueOf("0.00"), false); + + } + + @Test + public void testApplyLoanSpecificDueDatePenaltyWithDisbursementDate() { + + final LocalDate todaysDate = Utils.getLocalDateOfTenant(); + + // Client and Loan account creation + final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012"); + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper, null); + assertNotNull(getLoanProductsProductResponse); + + // Older date to have more than one overdue installment + LocalDate transactionDate = todaysDate; + String operationDate = Utils.dateFormatter.format(transactionDate); + log.info("Operation date {}", transactionDate); + + // Create Loan Account + final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(), + getLoanProductsProductResponse.getId().toString(), operationDate); + + // Get loan details + GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("0.00"), true); + + // Apply Loan Charge with specific due date + + final String feeAmount = "10.00"; + String payloadJSON = ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, feeAmount, true); + final PostChargesResponse postChargesResponse = ChargesHelper.createLoanCharge(requestSpec, responseSpec, payloadJSON); + assertNotNull(postChargesResponse); + final Long loanChargeId = postChargesResponse.getResourceId(); + assertNotNull(loanChargeId); + + payloadJSON = LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(loanChargeId.toString(), operationDate, feeAmount); + PostLoansLoanIdChargesResponse postLoansLoanIdChargesResponse = loanTransactionHelper.addChargeForLoan(loanId, payloadJSON, + responseSpec); + assertNotNull(postLoansLoanIdChargesResponse); + + // Get loan details expecting to have a delinquency classification + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf(principalAmount), Double.valueOf("10.00"), true); + + // Make a full repayment to close the Loan + Float amount = Float.valueOf("1010.00"); + PostLoansLoanIdTransactionsResponse loanIdTransactionsResponse = loanTransactionHelper.makeLoanRepayment(operationDate, amount, + loanId); + assertNotNull(loanIdTransactionsResponse); + log.info("Loan Transaction Id: {} {}", loanId, loanIdTransactionsResponse.getResourceId()); + + getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); + assertNotNull(getLoansLoanIdResponse); + validateLoanAccount(getLoansLoanIdResponse, Double.valueOf("0.00"), Double.valueOf("0.00"), true); + loanTransactionHelper.validateLoanStatus(getLoansLoanIdResponse, "loanStatusType.closed.obligations.met"); } @@ -137,11 +192,15 @@ public class LoanChargeSpecificDueDateTest { return loanId; } - private void validateLoanAccount(GetLoansLoanIdResponse getLoansLoanIdResponse, Double principal, Double fees) { + private void validateLoanAccount(GetLoansLoanIdResponse getLoansLoanIdResponse, Double principal, Double fees, boolean isPenalty) { assertNotNull(getLoansLoanIdResponse); loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, principal); - loanTransactionHelper.validateLoanFeesOustandingBalance(getLoansLoanIdResponse, fees); + if (isPenalty) { + loanTransactionHelper.validateLoanPenaltiesOustandingBalance(getLoansLoanIdResponse, fees); + } else { + loanTransactionHelper.validateLoanFeesOustandingBalance(getLoansLoanIdResponse, fees); + } loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, (principal + fees)); } 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 1eba90212..f2f079129 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 @@ -1563,6 +1563,13 @@ public class LoanTransactionHelper extends IntegrationTest { } } + public void validateLoanPenaltiesOustandingBalance(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) { + GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary(); + assertNotNull(getLoansLoanIdSummary); + log.info("Loan with Fees Outstanding Balance {} expected {}", getLoansLoanIdSummary.getFeeChargesOutstanding(), amountExpected); + assertEquals(amountExpected, getLoansLoanIdSummary.getPenaltyChargesOutstanding()); + } + public void validateLoanTotalOustandingBalance(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) { GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary(); if (getLoansLoanIdSummary != null) {