FINERACT-1785: Allow to add charge (penalty) with same disbursement date

This commit is contained in:
Jose Alberto Hernandez
2022-12-13 14:13:41 +01:00
committed by Arnold Gálovics
parent e938104b84
commit a987956cbc
5 changed files with 76 additions and 21 deletions
@@ -632,17 +632,6 @@ public class LoanCharge extends AbstractPersistableCustom {
return target != null && target.isAfter(fromNotInclusive) && !target.isAfter(upToAndInclusive); 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() { public boolean isFeeCharge() {
return !this.penaltyCharge; return !this.penaltyCharge;
} }
@@ -119,8 +119,7 @@ public class LoanRepaymentScheduleProcessingWrapper {
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount()); cumulative = cumulative.plus(loanCharge.amount());
// Special case for Loan Charges (Due Date) added the same disbursement date // Special case for Loan Charges (Due Date) added the same disbursement date
} else if (period.isFirstPeriod() } else if (period.isFirstPeriod() && periodStart.equals(loanCharge.getDueDate())) {
&& loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount()); cumulative = cumulative.plus(loanCharge.amount());
} }
} }
@@ -213,6 +212,9 @@ public class LoanRepaymentScheduleProcessingWrapper {
cumulative = cumulative.plus(loanChargeAmt); cumulative = cumulative.plus(loanChargeAmt);
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) { } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.amount()); 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());
} }
} }
} }
@@ -21,13 +21,11 @@ package org.apache.fineract.portfolio.loanaccount.domain;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException;
import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@Slf4j
@RequiredArgsConstructor @RequiredArgsConstructor
public class LoanRepaymentScheduleTransactionProcessorFactory { public class LoanRepaymentScheduleTransactionProcessorFactory {
@@ -62,7 +62,7 @@ public class LoanChargeSpecificDueDateTest {
} }
@Test @Test
public void testApplyLoanSpecificDueDateChargeWithDisbursementDate() { public void testApplyLoanSpecificDueDateFeeWithDisbursementDate() {
final LocalDate todaysDate = Utils.getLocalDateOfTenant(); final LocalDate todaysDate = Utils.getLocalDateOfTenant();
@@ -82,7 +82,7 @@ public class LoanChargeSpecificDueDateTest {
// Get loan details // Get loan details
GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); 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 // Apply Loan Charge with specific due date
@@ -99,7 +99,7 @@ public class LoanChargeSpecificDueDateTest {
// Get loan details expecting to have a delinquency classification // Get loan details expecting to have a delinquency classification
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); 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 // Make a full repayment to close the Loan
Float amount = Float.valueOf("1010.00"); Float amount = Float.valueOf("1010.00");
@@ -110,7 +110,62 @@ public class LoanChargeSpecificDueDateTest {
getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId); getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
assertNotNull(getLoansLoanIdResponse); 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; return loanId;
} }
private void validateLoanAccount(GetLoansLoanIdResponse getLoansLoanIdResponse, Double principal, Double fees) { private void validateLoanAccount(GetLoansLoanIdResponse getLoansLoanIdResponse, Double principal, Double fees, boolean isPenalty) {
assertNotNull(getLoansLoanIdResponse); assertNotNull(getLoansLoanIdResponse);
loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse); loanTransactionHelper.printRepaymentSchedule(getLoansLoanIdResponse);
loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, principal); loanTransactionHelper.validateLoanPrincipalOustandingBalance(getLoansLoanIdResponse, principal);
loanTransactionHelper.validateLoanFeesOustandingBalance(getLoansLoanIdResponse, fees); if (isPenalty) {
loanTransactionHelper.validateLoanPenaltiesOustandingBalance(getLoansLoanIdResponse, fees);
} else {
loanTransactionHelper.validateLoanFeesOustandingBalance(getLoansLoanIdResponse, fees);
}
loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, (principal + fees)); loanTransactionHelper.validateLoanTotalOustandingBalance(getLoansLoanIdResponse, (principal + fees));
} }
@@ -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) { public void validateLoanTotalOustandingBalance(GetLoansLoanIdResponse getLoansLoanIdResponse, Double amountExpected) {
GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary(); GetLoansLoanIdSummary getLoansLoanIdSummary = getLoansLoanIdResponse.getSummary();
if (getLoansLoanIdSummary != null) { if (getLoansLoanIdSummary != null) {