diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java index 643c26060..b530714c1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidator.java @@ -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 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 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 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; } } diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java new file mode 100644 index 000000000..fa95c4b00 --- /dev/null +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/reamortization/LoanReAmortizationValidatorTest.java @@ -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 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 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 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 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 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; + } + +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java index 20fae1996..c5a64bfc2 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reaging/LoanReAgingIntegrationTest.java @@ -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)// diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java index fd3946931..d902baf85 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java @@ -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