FINERACT-2060: Validation rules for loan reamortization
This commit is contained in:
committed by
taskain7
parent
c857527389
commit
f05be57c50
+102
-2
@@ -18,18 +18,118 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service.reamortization;
|
||||
|
||||
import static org.apache.fineract.infrastructure.core.service.DateUtils.getBusinessLocalDate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanReAmortizationApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ChargeOrTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class LoanReAmortizationValidator {
|
||||
|
||||
public void validateReAmortize(Loan loan, JsonCommand command) {
|
||||
// TODO: implement
|
||||
validateReAmortizeRequest(command);
|
||||
validateReAmortizeBusinessRules(loan);
|
||||
}
|
||||
|
||||
private void validateReAmortizeRequest(JsonCommand command) {
|
||||
List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||
DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan.reAmortization");
|
||||
|
||||
String externalId = command.stringValueOfParameterNamedAllowingNull(LoanReAmortizationApiConstants.externalIdParameterName);
|
||||
baseDataValidator.reset().parameter(LoanReAmortizationApiConstants.externalIdParameterName).ignoreIfNull().value(externalId)
|
||||
.notExceedingLengthOf(100);
|
||||
|
||||
throwExceptionIfValidationErrorsExist(dataValidationErrors);
|
||||
}
|
||||
|
||||
private void validateReAmortizeBusinessRules(Loan loan) {
|
||||
// validate reamortization shouldn't happen after maturity
|
||||
if (DateUtils.isAfter(getBusinessLocalDate(), loan.getMaturityDate())) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.cannot.be.submitted.after.maturity",
|
||||
"Loan cannot be re-amortized after maturity", loan.getId());
|
||||
}
|
||||
|
||||
// validate reamortization is only available for progressive schedule & advanced payment allocation
|
||||
LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loan.getLoanProductRelatedDetail().getLoanScheduleType().name());
|
||||
boolean isProgressiveSchedule = LoanScheduleType.PROGRESSIVE.equals(loanScheduleType);
|
||||
|
||||
String transactionProcessingStrategyCode = loan.getTransactionProcessingStrategyCode();
|
||||
boolean isAdvancedPaymentSchedule = AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||
.equals(transactionProcessingStrategyCode);
|
||||
|
||||
if (!(isProgressiveSchedule && isAdvancedPaymentSchedule)) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.supported.only.for.progressive.loan.schedule.type",
|
||||
"Loan reamortization is only available for progressive repayment schedule and Advanced payment allocation strategy",
|
||||
loan.getId());
|
||||
}
|
||||
|
||||
// validate reamortization is only available for non-interest bearing loans
|
||||
if (loan.isInterestBearing()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.supported.only.for.non.interest.loans",
|
||||
"Loan reamortization is only available for non-interest bearing loans", loan.getId());
|
||||
}
|
||||
|
||||
// validate reamortization is only done on an active loan
|
||||
if (!loan.getStatus().isActive()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.supported.only.for.active.loans",
|
||||
"Loan reamortization can only be done on active loans", loan.getId());
|
||||
}
|
||||
|
||||
// validate if there's already a re-amortization transaction for today
|
||||
boolean isReAmortizationTransactionForTodayPresent = loan.getLoanTransactions().stream()
|
||||
.anyMatch(tx -> tx.getTypeOf().isReAmortize() && tx.getTransactionDate().equals(getBusinessLocalDate()));
|
||||
if (isReAmortizationTransactionForTodayPresent) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.reamortize.transaction.already.present.for.today",
|
||||
"Loan reamortization can only be done once a day. There has already been a reamortization done for today",
|
||||
loan.getId());
|
||||
}
|
||||
}
|
||||
|
||||
public void validateUndoReAmortize(Loan loan, JsonCommand command) {
|
||||
// TODO: implement
|
||||
validateUndoReAmortizeBusinessRules(loan);
|
||||
}
|
||||
|
||||
private void validateUndoReAmortizeBusinessRules(Loan loan) {
|
||||
// validate if there's a reamortization transaction already
|
||||
Optional<LoanTransaction> optionalReAmortizationTx = loan.getLoanTransactions().stream().filter(tx -> tx.getTypeOf().isReAmortize())
|
||||
.min(Comparator.comparing(LoanTransaction::getTransactionDate));
|
||||
if (optionalReAmortizationTx.isEmpty()) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.reamortization.transaction.missing",
|
||||
"Undoing a reamortization can only be done if there was a reamortization already", loan.getId());
|
||||
}
|
||||
|
||||
// validate if there's no payment between the reamortization and today
|
||||
boolean repaymentExistsAfterReAmortization = loan.getLoanTransactions().stream()
|
||||
.anyMatch(tx -> tx.getTypeOf().isRepaymentType() && transactionHappenedAfterOther(tx, optionalReAmortizationTx.get()));
|
||||
if (repaymentExistsAfterReAmortization) {
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.loan.reamortize.repayment.exists.after.reamortization",
|
||||
"Undoing a reamortization can only be done if there hasn't been any repayment afterwards", loan.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void throwExceptionIfValidationErrorsExist(List<ApiParameterError> dataValidationErrors) {
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
|
||||
dataValidationErrors);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean transactionHappenedAfterOther(LoanTransaction transaction, LoanTransaction otherTransaction) {
|
||||
return new ChargeOrTransaction(transaction).compareTo(new ChargeOrTransaction(otherTransaction)) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* 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.service.reamortization;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.time.Clock;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanReAmortizationValidatorTest {
|
||||
|
||||
private final LocalDate actualDate = LocalDate.now(Clock.systemUTC());
|
||||
|
||||
private LoanReAmortizationValidator underTest = new LoanReAmortizationValidator();
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
ThreadLocalContextUtil.setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, actualDate)));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
ThreadLocalContextUtil.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldNotThrowException() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
underTest.validateReAmortize(loan, command);
|
||||
// then no exception thrown
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenExternalIdIsLongerThan100() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
JsonCommand command = jsonCommand(RandomStringUtils.randomAlphabetic(120));
|
||||
// when
|
||||
PlatformApiDataValidationException result = assertThrows(PlatformApiDataValidationException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanIsAfterMaturity() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
given(loan.getMaturityDate()).willReturn(actualDate.minusDays(2));
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.cannot.be.submitted.after.maturity");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanIsOnCumulativeSchedule() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanProductRelatedDetail().getLoanScheduleType()).willReturn(LoanScheduleType.CUMULATIVE);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode())
|
||||
.isEqualTo("error.msg.loan.reamortize.supported.only.for.progressive.loan.schedule.type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanIsNotOnAdvancedPaymentAllocation() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
given(loan.getTransactionProcessingStrategyCode())
|
||||
.willReturn(DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode())
|
||||
.isEqualTo("error.msg.loan.reamortize.supported.only.for.progressive.loan.schedule.type");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanIsInterestBearing() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
given(loan.isInterestBearing()).willReturn(true);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.supported.only.for.non.interest.loans");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanIsNotActive() {
|
||||
// given
|
||||
Loan loan = loan();
|
||||
given(loan.getStatus()).willReturn(LoanStatus.APPROVED);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.supported.only.for.active.loans");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateReAmortize_ShouldThrowException_WhenLoanAlreadyHasReAmortizationForToday() {
|
||||
// given
|
||||
List<LoanTransaction> transactions = List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, actualDate.minusDays(2)),
|
||||
loanTransaction(LoanTransactionType.REAMORTIZE, actualDate));
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanTransactions()).willReturn(transactions);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode())
|
||||
.isEqualTo("error.msg.loan.reamortize.reamortize.transaction.already.present.for.today");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUndoReAmortize_ShouldThrowException_WhenLoanDoesntHaveReAmortization() {
|
||||
// given
|
||||
List<LoanTransaction> transactions = List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, actualDate.minusDays(3)));
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanTransactions()).willReturn(transactions);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateUndoReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.reamortization.transaction.missing");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUndoReAmortize_ShouldThrowException_WhenLoanAlreadyHasRepaymentAfterReAmortization() {
|
||||
// given
|
||||
List<LoanTransaction> transactions = List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, actualDate.minusDays(3)),
|
||||
loanTransaction(LoanTransactionType.REAMORTIZE, actualDate.minusDays(2)),
|
||||
loanTransaction(LoanTransactionType.REPAYMENT, actualDate.minusDays(1)));
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanTransactions()).willReturn(transactions);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateUndoReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.repayment.exists.after.reamortization");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUndoReAmortize_ShouldThrowException_WhenLoanAlreadyHasRepaymentAfterReAmortization_SameDay() {
|
||||
// given
|
||||
List<LoanTransaction> transactions = List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, actualDate.minusDays(2)),
|
||||
loanTransaction(LoanTransactionType.REAMORTIZE, actualDate.minusDays(1),
|
||||
OffsetDateTime.of(actualDate, LocalTime.of(10, 0), ZoneOffset.UTC)),
|
||||
loanTransaction(LoanTransactionType.REPAYMENT, actualDate.minusDays(1),
|
||||
OffsetDateTime.of(actualDate, LocalTime.of(11, 0), ZoneOffset.UTC)));
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanTransactions()).willReturn(transactions);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
GeneralPlatformDomainRuleException result = assertThrows(GeneralPlatformDomainRuleException.class,
|
||||
() -> underTest.validateUndoReAmortize(loan, command));
|
||||
// then
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.getGlobalisationMessageCode()).isEqualTo("error.msg.loan.reamortize.repayment.exists.after.reamortization");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUndoReAmortize_ShouldNotThrowException_WhenLoanAlreadyHasRepaymentAfterReAmortization_SameDay_RepaymentBeforeReAmortization() {
|
||||
// given
|
||||
List<LoanTransaction> transactions = List.of(loanTransaction(LoanTransactionType.DISBURSEMENT, actualDate.minusDays(2)),
|
||||
loanTransaction(LoanTransactionType.REAMORTIZE, actualDate.minusDays(1),
|
||||
OffsetDateTime.of(actualDate, LocalTime.of(10, 0), ZoneOffset.UTC)),
|
||||
loanTransaction(LoanTransactionType.REPAYMENT, actualDate.minusDays(1),
|
||||
OffsetDateTime.of(actualDate, LocalTime.of(9, 0), ZoneOffset.UTC)));
|
||||
Loan loan = loan();
|
||||
given(loan.getLoanTransactions()).willReturn(transactions);
|
||||
JsonCommand command = jsonCommand();
|
||||
// when
|
||||
underTest.validateUndoReAmortize(loan, command);
|
||||
// then no exception thrown
|
||||
}
|
||||
|
||||
private JsonCommand jsonCommand() {
|
||||
return jsonCommand("123456");
|
||||
}
|
||||
|
||||
private JsonCommand jsonCommand(String externalId) {
|
||||
String json = """
|
||||
{
|
||||
"externalId": "%s"
|
||||
}
|
||||
""".formatted(externalId);
|
||||
FromJsonHelper fromJsonHelper = new FromJsonHelper();
|
||||
return new JsonCommand(1L, fromJsonHelper.parse(json), fromJsonHelper);
|
||||
}
|
||||
|
||||
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate, OffsetDateTime creationTime) {
|
||||
LoanTransaction loanTransaction = loanTransaction(type, txDate);
|
||||
given(loanTransaction.getCreatedDateTime()).willReturn(creationTime);
|
||||
return loanTransaction;
|
||||
}
|
||||
|
||||
private LoanTransaction loanTransaction(LoanTransactionType type, LocalDate txDate) {
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
given(loanTransaction.getTypeOf()).willReturn(type);
|
||||
given(loanTransaction.getTransactionDate()).willReturn(txDate);
|
||||
given(loanTransaction.getSubmittedOnDate()).willReturn(txDate);
|
||||
return loanTransaction;
|
||||
}
|
||||
|
||||
private Loan loan() {
|
||||
Loan loan = mock(Loan.class);
|
||||
given(loan.getStatus()).willReturn(LoanStatus.ACTIVE);
|
||||
given(loan.getMaturityDate()).willReturn(actualDate.plusDays(30));
|
||||
given(loan.getTransactionProcessingStrategyCode())
|
||||
.willReturn(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = mock(LoanProductRelatedDetail.class);
|
||||
given(loan.getLoanProductRelatedDetail()).willReturn(loanProductRelatedDetail);
|
||||
given(loanProductRelatedDetail.getLoanScheduleType()).willReturn(LoanScheduleType.PROGRESSIVE);
|
||||
given(loan.isInterestBearing()).willReturn(false);
|
||||
given(loan.getLoanTransactions()).willReturn(List.of());
|
||||
return loan;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-2
@@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
|
||||
@@ -43,7 +44,7 @@ public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
|
||||
@@ -55,6 +56,7 @@ public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
|
||||
double amount = 1250.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
@@ -112,7 +114,7 @@ public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
|
||||
@@ -124,6 +126,7 @@ public class LoanReAgingIntegrationTest extends BaseLoanIntegrationTest {
|
||||
double amount = 1250.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
|
||||
+15
-10
@@ -27,6 +27,7 @@ import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
@@ -39,11 +40,11 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 1;
|
||||
int numberOfRepayments = 2;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
|
||||
@@ -55,6 +56,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
double amount = 1250.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
@@ -78,7 +80,8 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// verify schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(1250.0, false, "01 February 2023") //
|
||||
installment(625.0, false, "01 February 2023"), //
|
||||
installment(625.0, false, "01 March 2023") //
|
||||
);
|
||||
|
||||
createdLoanId.set(loanId);
|
||||
@@ -93,7 +96,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023"), //
|
||||
transaction(1250.0, "Re-amortize", "02 February 2023") //
|
||||
transaction(625.0, "Re-amortize", "02 February 2023") //
|
||||
);
|
||||
|
||||
// TODO: verify installments when schedule generation is implemented
|
||||
@@ -108,11 +111,11 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 1;
|
||||
int numberOfRepayments = 2;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()); //
|
||||
@@ -124,6 +127,7 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
double amount = 1250.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
@@ -147,7 +151,8 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// verify schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(1250.0, false, "01 February 2023") //
|
||||
installment(625.0, false, "01 February 2023"), //
|
||||
installment(625.0, false, "01 March 2023") //
|
||||
);
|
||||
|
||||
createdLoanId.set(loanId);
|
||||
@@ -162,20 +167,20 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest {
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023"), //
|
||||
transaction(1250.0, "Re-amortize", "02 February 2023") //
|
||||
transaction(625.0, "Re-amortize", "02 February 2023") //
|
||||
);
|
||||
});
|
||||
|
||||
runAt("03 February 2023", () -> {
|
||||
long loanId = createdLoanId.get();
|
||||
|
||||
// create re-amortize transaction
|
||||
// undo re-amortize transaction
|
||||
undoReAmortizeLoan(loanId);
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023"), //
|
||||
reversedTransaction(1250.0, "Re-amortize", "02 February 2023") //
|
||||
reversedTransaction(625.0, "Re-amortize", "02 February 2023") //
|
||||
);
|
||||
|
||||
// TODO: verify installments when schedule generation is implemented
|
||||
|
||||
Reference in New Issue
Block a user