diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 5f7275fa6..8c7b42477 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2955,7 +2955,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { } - public LoanTransaction handleDownPayment(final BigDecimal disbursedAmount, final JsonCommand command, + public LoanTransaction handleDownPayment(final LoanTransaction disbursementTransaction, final JsonCommand command, final ScheduleGeneratorDTO scheduleGeneratorDTO) { LocalDate disbursedOn = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE); BigDecimal disbursedAmountPercentageForDownPayment = this.loanRepaymentScheduleDetail.getDisbursedAmountPercentageForDownPayment(); @@ -2964,19 +2964,26 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { externalId = ExternalId.generate(); } Money downPaymentMoney = Money.of(getCurrency(), - MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19)); - LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn, externalId); + MathUtil.percentageOf(disbursementTransaction.getAmount(), disbursedAmountPercentageForDownPayment, 19)); - LoanEvent event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; - validateRepaymentTypeAccountStatus(downPaymentTransaction, event); - HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); - validateRepaymentDateIsOnHoliday(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), - holidayDetailDTO.getHolidays()); - validateRepaymentDateIsOnNonWorkingDay(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), - holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); + Money adjustedDownPaymentMoney = MathUtil + .negativeToZero(downPaymentMoney.minus(disbursementTransaction.getOverPaymentPortion(getCurrency()))); + if (adjustedDownPaymentMoney.isGreaterThanZero()) { + LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), adjustedDownPaymentMoney, null, disbursedOn, + externalId); + LoanEvent event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; + validateRepaymentTypeAccountStatus(downPaymentTransaction, event); + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); + validateRepaymentDateIsOnHoliday(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(), + holidayDetailDTO.getHolidays()); + validateRepaymentDateIsOnNonWorkingDay(downPaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(), + holidayDetailDTO.isAllowTransactionsOnNonWorkingDay()); - handleRepaymentOrRecoveryOrWaiverTransaction(downPaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); - return downPaymentTransaction; + handleRepaymentOrRecoveryOrWaiverTransaction(downPaymentTransaction, loanLifecycleStateMachine, null, scheduleGeneratorDTO); + return downPaymentTransaction; + } else { + return null; + } } public boolean isAutoRepaymentForDownPaymentEnabled() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 1afa4f617..53de2b0ef 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -465,10 +465,13 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep newLoanTransaction.adjustInterestComponent(currency); } /* - * Check if the transaction amounts have changed. If so, reverse the original transaction and update - * changedTransactionDetail accordingly + * Check if the transaction amounts have changed or was there any transaction for the same date which was + * reverse-replayed. If so, reverse the original transaction and update changedTransactionDetail accordingly */ - if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) { + boolean aTransactionWasAlreadyReplayedForTheSameDate = changedTransactionDetail.getNewTransactionMappings().values().stream() + .anyMatch(lt -> lt.getTransactionDate().equals(loanTransaction.getTransactionDate())); + if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction) + && !aTransactionWasAlreadyReplayedForTheSameDate) { loanTransaction.updateLoanTransactionToRepaymentScheduleMappings( newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings()); } else { @@ -503,6 +506,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep private void updateLoanSchedule(LoanTransaction disbursementTransaction, MonetaryCurrency currency, List installments, MoneyHolder overpaymentHolder) { + disbursementTransaction.resetDerivedComponents(); final MathContext mc = MoneyHelper.getMathContext(); List candidateRepaymentInstallments = installments.stream().filter( i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional()) @@ -570,9 +574,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep transactionAmountUnprocessed = processPeriodsVertically(loanTransaction, currency, installments, overpaymentHolder.getMoneyObject(), defaultPaymentAllocationRule, transactionMappings, Set.of(), balances); } - if (transactionAmountUnprocessed != null && transactionAmountUnprocessed.isGreaterThanZero()) { - overpaymentHolder.setMoneyObject(transactionAmountUnprocessed); - } + overpaymentHolder.setMoneyObject(transactionAmountUnprocessed); loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java index 1bf1564cd..57ee4e9c3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerService.java @@ -19,12 +19,12 @@ package org.apache.fineract.portfolio.loanaccount.service; import org.apache.fineract.infrastructure.core.api.JsonCommand; -import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; public interface LoanDownPaymentHandlerService { - LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, Money amountToDisburse, Loan loan); + LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, + LoanTransaction disbursementTransaction, Loan loan); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java index 1825fff8f..8fdc76e98 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java @@ -25,7 +25,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalance import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPostBusinessEvent; import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; -import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @@ -39,13 +38,15 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler private final BusinessEventNotifierService businessEventNotifierService; @Override - public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, Money amountToDisburse, - Loan loan) { + public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command, + LoanTransaction disbursementTransaction, Loan loan) { businessEventNotifierService.notifyPreBusinessEvent(new LoanTransactionDownPaymentPreBusinessEvent(loan)); - LoanTransaction downPaymentTransaction = loan.handleDownPayment(amountToDisburse.getAmount(), command, scheduleGeneratorDTO); - LoanTransaction savedLoanTransaction = loanTransactionRepository.saveAndFlush(downPaymentTransaction); - businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionDownPaymentPostBusinessEvent(savedLoanTransaction)); - businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); - return savedLoanTransaction; + LoanTransaction downPaymentTransaction = loan.handleDownPayment(disbursementTransaction, command, scheduleGeneratorDTO); + if (downPaymentTransaction != null) { + downPaymentTransaction = loanTransactionRepository.saveAndFlush(downPaymentTransaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanTransactionDownPaymentPostBusinessEvent(downPaymentTransaction)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + } + return downPaymentTransaction; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 565812c8e..a34a3071f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -485,7 +485,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } else { - loanDownPaymentHandlerService.handleDownPayment(scheduleGeneratorDTO, command, amountToDisburse, loan); + loanDownPaymentHandlerService.handleDownPayment(scheduleGeneratorDTO, command, disbursementTransaction, loan); } } } @@ -563,6 +563,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf businessEventNotifierService.notifyPostBusinessEvent(new LoanDisbursalTransactionBusinessEvent(disbursalTransaction)); } + loan.updateLoanSummaryAndStatus(); + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loan.getId()) // diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java index eae94acf4..0bfa16ad2 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java @@ -19,13 +19,13 @@ package org.apache.fineract.portfolio.loanaccount.service; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.math.BigDecimal; import java.math.RoundingMode; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent; @@ -33,7 +33,6 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.transaction import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionDownPaymentPreBusinessEvent; import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; -import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; @@ -88,19 +87,19 @@ public class LoanDownPaymentHandlerServiceImplTest { public void testDownPaymentHandler() { // given Loan loanForProcessing = Mockito.mock(Loan.class); + LoanTransaction disbursement = Mockito.mock(LoanTransaction.class); MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); doNothing().when(businessEventNotifierService).notifyPreBusinessEvent(any(LoanTransactionDownPaymentPreBusinessEvent.class)); doNothing().when(businessEventNotifierService).notifyPostBusinessEvent(any(LoanTransactionDownPaymentPostBusinessEvent.class)); doNothing().when(businessEventNotifierService).notifyPostBusinessEvent(any(LoanBalanceChangedBusinessEvent.class)); when(loanTransactionRepository.saveAndFlush(any(LoanTransaction.class))).thenReturn(loanTransaction); - when(loanForProcessing.handleDownPayment(any(BigDecimal.class), eq(command), eq(scheduleGeneratorDTO))).thenReturn(loanTransaction); + when(loanForProcessing.handleDownPayment(eq(disbursement), eq(command), eq(scheduleGeneratorDTO))).thenReturn(loanTransaction); when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); when(loanCurrency.getCode()).thenReturn("CODE"); when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1); when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1); - Money amount = Money.of(loanCurrency, BigDecimal.TEN); // when - LoanTransaction actual = underTest.handleDownPayment(scheduleGeneratorDTO, command, amount, loanForProcessing); + LoanTransaction actual = underTest.handleDownPayment(scheduleGeneratorDTO, command, disbursement, loanForProcessing); // then assertNotNull(actual); @@ -109,6 +108,32 @@ public class LoanDownPaymentHandlerServiceImplTest { verify(businessEventNotifierService, Mockito.times(1)) .notifyPostBusinessEvent(Mockito.any(LoanTransactionDownPaymentPostBusinessEvent.class)); verify(businessEventNotifierService, Mockito.times(1)).notifyPostBusinessEvent(Mockito.any(LoanBalanceChangedBusinessEvent.class)); - verify(loanForProcessing, Mockito.times(1)).handleDownPayment(any(BigDecimal.class), eq(command), eq(scheduleGeneratorDTO)); + verify(loanForProcessing, Mockito.times(1)).handleDownPayment(eq(disbursement), eq(command), eq(scheduleGeneratorDTO)); + } + + @Test + public void testDownPaymentHandlerNoNewTransaction() { + // given + Loan loanForProcessing = Mockito.mock(Loan.class); + LoanTransaction disbursement = Mockito.mock(LoanTransaction.class); + MonetaryCurrency loanCurrency = Mockito.mock(MonetaryCurrency.class); + doNothing().when(businessEventNotifierService).notifyPreBusinessEvent(any(LoanTransactionDownPaymentPreBusinessEvent.class)); + when(loanForProcessing.handleDownPayment(eq(disbursement), eq(command), eq(scheduleGeneratorDTO))).thenReturn(null); + when(loanForProcessing.getCurrency()).thenReturn(loanCurrency); + when(loanCurrency.getCode()).thenReturn("CODE"); + when(loanCurrency.getCurrencyInMultiplesOf()).thenReturn(1); + when(loanCurrency.getDigitsAfterDecimal()).thenReturn(1); + // when + LoanTransaction actual = underTest.handleDownPayment(scheduleGeneratorDTO, command, disbursement, loanForProcessing); + + // then + assertNull(actual); + verify(businessEventNotifierService, Mockito.times(1)) + .notifyPreBusinessEvent(Mockito.any(LoanTransactionDownPaymentPreBusinessEvent.class)); + verify(businessEventNotifierService, Mockito.never()) + .notifyPostBusinessEvent(Mockito.any(LoanTransactionDownPaymentPostBusinessEvent.class)); + verify(businessEventNotifierService, Mockito.never()).notifyPostBusinessEvent(Mockito.any(LoanBalanceChangedBusinessEvent.class)); + verify(loanForProcessing, Mockito.times(1)).handleDownPayment(eq(disbursement), eq(command), eq(scheduleGeneratorDTO)); + verify(loanTransactionRepository, Mockito.never()).saveAndFlush(any(LoanTransaction.class)); } } 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 f6723cf77..d6cb8f4f5 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 @@ -3264,7 +3264,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan validateRepaymentPeriod(loanDetails, 3, LocalDate.of(2023, 12, 7), 32.0, 32.0, 0.0, 32.0, 0.0); validateRepaymentPeriod(loanDetails, 4, LocalDate.of(2023, 12, 22), 32.0, 32.0, 0.0, 32.0, 0.0); validateRepaymentPeriod(loanDetails, 5, LocalDate.of(2024, 1, 6), 32.0, 32.0, 0.0, 32.0, 0.0); - assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getStatus().getOverpaid()); verifyTransactions(loanResponse.getLoanId(), // transaction(100, "Disbursement", "22 November 2023", 100.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), // diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java index 2b11a04b1..3dc8891ab 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductWithDownPaymentConfigurationTest.java @@ -19,9 +19,7 @@ package org.apache.fineract.integrationtests; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; import io.restassured.builder.RequestSpecBuilder; import io.restassured.builder.ResponseSpecBuilder; @@ -29,29 +27,14 @@ import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; -import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; -import java.util.UUID; import org.apache.fineract.client.models.GetDelinquencyBucketsResponse; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; -import org.apache.fineract.client.models.GetLoansLoanIdResponse; -import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; -import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; -import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; -import org.apache.fineract.infrastructure.core.service.DateUtils; -import org.apache.fineract.integrationtests.common.BusinessDateHelper; -import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; -import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.Utils; -import org.apache.fineract.integrationtests.common.accounting.Account; -import org.apache.fineract.integrationtests.common.accounting.AccountHelper; -import org.apache.fineract.integrationtests.common.accounting.JournalEntry; -import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; -import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; @@ -63,9 +46,6 @@ public class LoanProductWithDownPaymentConfigurationTest { private ResponseSpecification responseSpec; private RequestSpecification requestSpec; private LoanTransactionHelper loanTransactionHelper; - private ClientHelper clientHelper; - private AccountHelper accountHelper; - private JournalEntryHelper journalEntryHelper; @BeforeEach public void setup() { @@ -74,9 +54,6 @@ public class LoanProductWithDownPaymentConfigurationTest { this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); - this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); - this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec); } @Test @@ -198,412 +175,6 @@ public class LoanProductWithDownPaymentConfigurationTest { loanProductErrorData.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); } - @Test - public void loanApplicationCreationWithLoanProductWithEnableDownPaymentConfiguration() { - // Loan ExternalId - String loanExternalIdStr = UUID.randomUUID().toString(); - - // Delinquency Bucket - final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); - final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, - delinquencyBucketId); - - // down-payment configuration - Boolean enableDownPayment = true; - BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); - Boolean enableAutoRepaymentForDownPayment = false; - - final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); - - // Loan Product creation with down-payment configuration - Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, - "25", enableAutoRepaymentForDownPayment); - - final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); - assertNotNull(getLoanProductsProductResponse); - assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() - .compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); - - final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); - - // Retrieve Loan with loanId - - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - - } - - @Test - public void loanApplicationWithLoanProductWithEnableDownPaymentConfigurationDoesNotChangeWithUpdateProductConfiguration() { - // Loan ExternalId - String loanExternalIdStr = UUID.randomUUID().toString(); - - // Delinquency Bucket - final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); - final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, - delinquencyBucketId); - - // down-payment configuration - Boolean enableDownPayment = true; - BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(12.5); - Boolean enableAutoRepaymentForDownPayment = false; - - final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); - - // Loan Product creation with down-payment configuration - Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, - "12.5", enableAutoRepaymentForDownPayment); - - final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); - assertNotNull(getLoanProductsProductResponse); - assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() - .compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); - - final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); - - // Retrieve Loan with loanId - - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - - // Modify Loan Product to update enable down payment configuration - PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper, - getLoanProductsProductResponse.getId()); - assertNotNull(loanProductModifyResponse); - - // verify Loan product configuration change - GetLoanProductsProductIdResponse getLoanProductsProductResponse_1 = loanTransactionHelper.getLoanProduct(loanProductId); - assertNotNull(getLoanProductsProductResponse_1); - assertEquals(enableDownPayment, getLoanProductsProductResponse_1.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse_1.getDisbursedAmountPercentageForDownPayment().compareTo(BigDecimal.valueOf(25.0))); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse_1.getEnableAutoRepaymentForDownPayment()); - - // make repayment for loan - final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, - new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") - .transactionAmount(100.0)); - - loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan does not change - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - - } - - @Test - public void loanApplicationWithLoanProductWithEnableDownPaymentAndEnableAutoRepaymentForDownPaymentTest() { - try { - - // Set business date - LocalDate disbursementDate = LocalDate.of(2023, 3, 3); - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); - - // Accounts oof periodic accrual - final Account assetAccount = this.accountHelper.createAssetAccount(); - final Account incomeAccount = this.accountHelper.createIncomeAccount(); - final Account expenseAccount = this.accountHelper.createExpenseAccount(); - final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); - - // Loan ExternalId - String loanExternalIdStr = UUID.randomUUID().toString(); - - // Delinquency Bucket - final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); - final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, - delinquencyBucketId); - - // down-payment configuration - Boolean enableDownPayment = true; - BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); - Boolean enableAutoRepaymentForDownPayment = true; - - final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); - - // Loan Product creation with down-payment configuration - final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( - loanTransactionHelper, delinquencyBucketId, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, - incomeAccount, expenseAccount, overpaymentAccount); - - assertNotNull(getLoanProductsProductResponse); - assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() - .compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); - - final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), - loanExternalIdStr); - - // Retrieve Loan with loanId - - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - - // first disbursement - loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000"); - - loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - // verify down-payment transaction created - checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f, 0.0f, loanId); - - // verify journal entries for down-payment - this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", - new JournalEntry(250, JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", - new JournalEntry(250, JournalEntry.TransactionType.DEBIT)); - - // verify installment details - assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate()); - assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding()); - assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); - assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); - assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod()); - assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); - assertEquals(LocalDate.of(2023, 4, 2), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); - assertEquals(750.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); - assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod()); - - // second disbursement - - disbursementDate = LocalDate.of(2023, 3, 5); - BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); - loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "200"); - checkDownPaymentTransaction(disbursementDate, 50.0f, 0.0f, 0.0f, 0.0f, loanId); - - loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - // verify installment details - assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate()); - assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding()); - assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); - assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); - assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); - assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod()); - assertEquals(LocalDate.of(2023, 3, 5), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); - assertEquals(200.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalLoanBalanceOutstanding()); - assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod()); - assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); - assertEquals(LocalDate.of(2023, 3, 5), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); - assertEquals(50.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); - assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getDownPaymentPeriod()); - assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); - assertEquals(LocalDate.of(2023, 4, 2), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); - assertEquals(900.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); - assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getDownPaymentPeriod()); - - // verify journal entries for down-payment - this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "05 March 2023", - new JournalEntry(50, JournalEntry.TransactionType.CREDIT)); - this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "05 March 2023", - new JournalEntry(50, JournalEntry.TransactionType.DEBIT)); - - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } - - } - - @Test - public void loanApplicationWithLoanProductWithEnableDownPaymentAndDisableAutoRepaymentForDownPaymentVerifyNoDownPaymentCreatedTest() { - try { - - // Set business date - LocalDate disbursementDate = LocalDate.of(2023, 3, 3); - - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); - BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); - - // Accounts oof periodic accrual - final Account assetAccount = this.accountHelper.createAssetAccount(); - final Account incomeAccount = this.accountHelper.createIncomeAccount(); - final Account expenseAccount = this.accountHelper.createExpenseAccount(); - final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); - - // Loan ExternalId - String loanExternalIdStr = UUID.randomUUID().toString(); - - // Delinquency Bucket - final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); - final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, - delinquencyBucketId); - - // down-payment configuration - Boolean enableDownPayment = true; - BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); - Boolean enableAutoRepaymentForDownPayment = false; - - final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); - - // Loan Product creation with down-payment configuration - final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( - loanTransactionHelper, delinquencyBucketId, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, - incomeAccount, expenseAccount, overpaymentAccount); - - assertNotNull(getLoanProductsProductResponse); - assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() - .compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); - - final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), - loanExternalIdStr); - - // Retrieve Loan with loanId - - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - - // first disbursement - loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000"); - - // verify no down-payment transaction created - checkNoDownPaymentTransaction(loanId); - - } finally { - GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); - } - - } - - @Test - public void loanProductAndLoanAccountCreationWithEnableDownPaymentAndDisableRepaymentScheduleExtensionConfigurationTest() { - // Loan ExternalId - String loanExternalIdStr = UUID.randomUUID().toString(); - - // down-payment configuration - Boolean enableDownPayment = true; - BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); - Boolean enableAutoRepaymentForDownPayment = false; - - final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); - - // Loan Product creation with down-payment configuration - GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithEnableDownPaymentAndMultipleDisbursementsWithDisableRepaymentConfiguration( - loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment); - assertNotNull(getLoanProductsProductResponse); - assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); - assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() - .compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); - - final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), - loanExternalIdStr); - - // Retrieve Loan with loanId - - GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); - - // verify down-payment details for Loan - assertNotNull(loanDetails); - assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); - assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); - assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); - } - - private void checkNoDownPaymentTransaction(final Integer loanID) { - ArrayList transactions = (ArrayList) loanTransactionHelper.getLoanTransactions(this.requestSpec, - this.responseSpec, loanID); - boolean isTransactionFound = false; - for (int i = 0; i < transactions.size(); i++) { - HashMap transactionType = (HashMap) transactions.get(i).get("type"); - boolean isDownPaymentTransaction = (Boolean) transactionType.get("downPayment"); - - if (isDownPaymentTransaction) { - isTransactionFound = true; - break; - } - } - assertFalse(isTransactionFound, "Down Payment entries are posted"); - } - - private void checkDownPaymentTransaction(final LocalDate transactionDate, final Float principalPortion, final Float interestPortion, - final Float feePortion, final Float penaltyPortion, final Integer loanID) { - ArrayList transactions = (ArrayList) loanTransactionHelper.getLoanTransactions(this.requestSpec, - this.responseSpec, loanID); - boolean isTransactionFound = false; - for (int i = 0; i < transactions.size(); i++) { - HashMap transactionType = (HashMap) transactions.get(i).get("type"); - boolean isDownPaymentTransaction = (Boolean) transactionType.get("downPayment"); - - if (isDownPaymentTransaction) { - ArrayList downPaymentDateAsArray = (ArrayList) transactions.get(i).get("date"); - LocalDate downPaymentEntryDate = LocalDate.of(downPaymentDateAsArray.get(0), downPaymentDateAsArray.get(1), - downPaymentDateAsArray.get(2)); - - if (DateUtils.isEqual(transactionDate, downPaymentEntryDate)) { - isTransactionFound = true; - assertEquals(principalPortion, Float.valueOf(String.valueOf(transactions.get(i).get("principalPortion"))), - "Mismatch in transaction amounts"); - assertEquals(interestPortion, Float.valueOf(String.valueOf(transactions.get(i).get("interestPortion"))), - "Mismatch in transaction amounts"); - assertEquals(feePortion, Float.valueOf(String.valueOf(transactions.get(i).get("feeChargesPortion"))), - "Mismatch in transaction amounts"); - assertEquals(penaltyPortion, Float.valueOf(String.valueOf(transactions.get(i).get("penaltyChargesPortion"))), - "Mismatch in transaction amounts"); - break; - } - } - } - assertTrue(isTransactionFound, "No Down Payment entries are posted"); - } - - private Integer createLoanAccountMultipleRepaymentsDisbursement(final Integer clientID, final Long loanProductID, - final String externalId) { - - String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30") - .withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays() - .withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() - .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03 March 2023") - .withSubmittedOnDate("03 March 2023").withLoanType("individual").withExternalId(externalId) - .build(clientID.toString(), loanProductID.toString(), null); - - final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); - loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId, null); - return loanId; - } - - private GetLoanProductsProductIdResponse createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( - LoanTransactionHelper loanTransactionHelper, Integer delinquencyBucketId, Boolean enableDownPayment, - String disbursedAmountPercentageForDownPayment, boolean enableAutoRepaymentForDownPayment, final Account... accounts) { - final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() - .withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") - .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() - .withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30") - .withDaysInYear("365").withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true) - .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) - .build(null); - final Integer loanProductId = loanTransactionHelper.getLoanProductId(loanProductJSON); - return loanTransactionHelper.getLoanProduct(loanProductId); - } - private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) { // down-payment configuration Boolean enableDownPayment = true; @@ -629,34 +200,4 @@ public class LoanProductWithDownPaymentConfigurationTest { final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap)); return loanProductId; } - - private Integer createApproveAndDisburseLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) { - - String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1") - .withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1") - .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance() - .withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod() - .withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual") - .withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null); - - final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); - loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null); - loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanId, "1000"); - return loanId; - } - - private GetLoanProductsProductIdResponse createLoanProductWithEnableDownPaymentAndMultipleDisbursementsWithDisableRepaymentConfiguration( - LoanTransactionHelper loanTransactionHelper, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment, - boolean enableAutoRepaymentForDownPayment) { - final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() - .withRepaymentAfterEvery("1").withNumberOfRepayments("3").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") - .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() - .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30").withDaysInYear("365") - .withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true) - .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) - .build(null); - final Integer loanProductId = loanTransactionHelper.getLoanProductId(loanProductJSON); - return loanTransactionHelper.getLoanProduct(loanProductId); - } - } 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 3dd00a8d0..2db85269b 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 @@ -18,7 +18,9 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.DATETIME_PATTERN; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -29,45 +31,71 @@ import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.GetDelinquencyBucketsResponse; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdSummary; +import org.apache.fineract.client.models.PaymentAllocationOrder; import org.apache.fineract.client.models.PostChargesResponse; import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdRequest; +import org.apache.fineract.client.models.PutLoanProductsProductIdResponse; import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.integrationtests.common.BusinessDateHelper; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.accounting.AccountHelper; +import org.apache.fineract.integrationtests.common.accounting.JournalEntry; +import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; +import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; +import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +@ExtendWith(LoanTestLifecycleExtension.class) public class LoanRepaymentScheduleWithDownPaymentTest { private ResponseSpecification responseSpec; private RequestSpecification requestSpec; private LoanTransactionHelper loanTransactionHelper; private ClientHelper clientHelper; + private AccountHelper accountHelper; + private JournalEntryHelper journalEntryHelper; @BeforeEach public void setup() { Utils.initializeRESTAssured(); - this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); - this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); - this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); - this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + loanTransactionHelper = new LoanTransactionHelper(requestSpec, responseSpec); + clientHelper = new ClientHelper(requestSpec, responseSpec); + accountHelper = new AccountHelper(requestSpec, responseSpec); + journalEntryHelper = new JournalEntryHelper(requestSpec, responseSpec); } @Test @@ -490,7 +518,7 @@ public class LoanRepaymentScheduleWithDownPaymentTest { final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, - "25", enableAutoRepaymentForDownPayment, true); + "25", enableAutoRepaymentForDownPayment, false); final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); assertNotNull(getLoanProductsProductResponse); @@ -876,6 +904,658 @@ public class LoanRepaymentScheduleWithDownPaymentTest { } } + @Test + public void loanApplicationCreationWithLoanProductWithEnableDownPaymentConfiguration() { + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = false; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, + "25", enableAutoRepaymentForDownPayment, false); + + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + } + + @Test + public void loanApplicationWithLoanProductWithEnableDownPaymentConfigurationDoesNotChangeWithUpdateProductConfiguration() { + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(12.5); + Boolean enableAutoRepaymentForDownPayment = false; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + Integer loanProductId = createLoanProductWithDownPaymentConfiguration(loanTransactionHelper, delinquencyBucketId, enableDownPayment, + "12.5", enableAutoRepaymentForDownPayment, false); + + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createApproveAndDisburseLoanAccount(clientId, loanProductId.longValue(), loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // Modify Loan Product to update enable down payment configuration + PutLoanProductsProductIdResponse loanProductModifyResponse = updateLoanProduct(loanTransactionHelper, + getLoanProductsProductResponse.getId()); + assertNotNull(loanProductModifyResponse); + + // verify Loan product configuration change + GetLoanProductsProductIdResponse getLoanProductsProductResponse_1 = loanTransactionHelper.getLoanProduct(loanProductId); + assertNotNull(getLoanProductsProductResponse_1); + assertEquals(enableDownPayment, getLoanProductsProductResponse_1.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse_1.getDisbursedAmountPercentageForDownPayment().compareTo(BigDecimal.valueOf(25.0))); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse_1.getEnableAutoRepaymentForDownPayment()); + + // make repayment for loan + final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("5 September 2022").locale("en") + .transactionAmount(100.0)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan does not change + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + } + + @Test + public void loanApplicationWithLoanProductWithEnableDownPaymentAndEnableAutoRepaymentForDownPaymentTest() { + try { + + // Set business date + LocalDate disbursementDate = LocalDate.of(2023, 3, 3); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + // Accounts oof periodic accrual + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = true; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( + loanTransactionHelper, delinquencyBucketId, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, + incomeAccount, expenseAccount, overpaymentAccount); + + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // first disbursement + loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000"); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + // verify down-payment transaction created + checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f, 0.0f, loanId); + + // verify journal entries for down-payment + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", + new JournalEntry(250, JournalEntry.TransactionType.CREDIT)); + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", + new JournalEntry(250, JournalEntry.TransactionType.DEBIT)); + + // verify installment details + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate()); + assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding()); + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod()); + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); + assertEquals(LocalDate.of(2023, 4, 2), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(750.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod()); + + // second disbursement + + disbursementDate = LocalDate.of(2023, 3, 5); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "200"); + checkDownPaymentTransaction(disbursementDate, 50.0f, 0.0f, 0.0f, 0.0f, loanId); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + // verify installment details + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate()); + assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding()); + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod()); + assertEquals(LocalDate.of(2023, 3, 5), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(200.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPrincipalLoanBalanceOutstanding()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod()); + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(3).getPeriod()); + assertEquals(LocalDate.of(2023, 3, 5), loanDetails.getRepaymentSchedule().getPeriods().get(3).getDueDate()); + assertEquals(50.0, loanDetails.getRepaymentSchedule().getPeriods().get(3).getTotalInstallmentAmountForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(3).getDownPaymentPeriod()); + assertEquals(3, loanDetails.getRepaymentSchedule().getPeriods().get(4).getPeriod()); + assertEquals(LocalDate.of(2023, 4, 2), loanDetails.getRepaymentSchedule().getPeriods().get(4).getDueDate()); + assertEquals(900.0, loanDetails.getRepaymentSchedule().getPeriods().get(4).getTotalInstallmentAmountForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(4).getDownPaymentPeriod()); + + // verify journal entries for down-payment + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "05 March 2023", + new JournalEntry(50, JournalEntry.TransactionType.CREDIT)); + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "05 March 2023", + new JournalEntry(50, JournalEntry.TransactionType.DEBIT)); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + + } + + @Test + public void loanApplicationWithLoanProductWithEnableDownPaymentAndDisableAutoRepaymentForDownPaymentVerifyNoDownPaymentCreatedTest() { + try { + + // Set business date + LocalDate disbursementDate = LocalDate.of(2023, 3, 3); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + // Accounts oof periodic accrual + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Delinquency Bucket + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec); + final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec, + delinquencyBucketId); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = false; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( + loanTransactionHelper, delinquencyBucketId, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, + incomeAccount, expenseAccount, overpaymentAccount); + + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // first disbursement + loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000"); + + // verify no down-payment transaction created + checkNoDownPaymentTransaction(loanId); + + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + + } + + @Test + public void loanProductAndLoanAccountCreationWithEnableDownPaymentAndDisableRepaymentScheduleExtensionConfigurationTest() { + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = false; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProductWithEnableDownPaymentAndMultipleDisbursementsWithDisableRepaymentConfiguration( + loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment); + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createLoanAccountMultipleRepaymentsDisbursement(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + } + + @Test + public void downPaymentOnOverpaidLoan() { + try { + + // Set business date + LocalDate disbursementDate = LocalDate.of(2023, 3, 3); + + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + + // Accounts oof periodic accrual + final Account assetAccount = accountHelper.createAssetAccount(); + final Account incomeAccount = accountHelper.createIncomeAccount(); + final Account expenseAccount = accountHelper.createExpenseAccount(); + final Account overpaymentAccount = accountHelper.createLiabilityAccount(); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25); + Boolean enableAutoRepaymentForDownPayment = true; + + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + // Loan Product creation with down-payment configuration + final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting( + loanTransactionHelper, enableDownPayment, "25", enableAutoRepaymentForDownPayment, assetAccount, incomeAccount, + expenseAccount, overpaymentAccount); + + assertNotNull(getLoanProductsProductResponse); + assertEquals(enableDownPayment, getLoanProductsProductResponse.getEnableDownPayment()); + assertEquals(0, getLoanProductsProductResponse.getDisbursedAmountPercentageForDownPayment() + .compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, getLoanProductsProductResponse.getEnableAutoRepaymentForDownPayment()); + + final Integer loanId = createLoanAccountWithAdvancedPaymentAllocation(clientId, getLoanProductsProductResponse.getId(), + loanExternalIdStr); + + // Retrieve Loan with loanId + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + // verify down-payment details for Loan + assertNotNull(loanDetails); + assertEquals(enableDownPayment, loanDetails.getEnableDownPayment()); + assertEquals(0, loanDetails.getDisbursedAmountPercentageForDownPayment().compareTo(disbursedAmountPercentageForDownPayment)); + assertEquals(enableAutoRepaymentForDownPayment, loanDetails.getEnableAutoRepaymentForDownPayment()); + + // first disbursement + loanTransactionHelper.disburseLoanWithTransactionAmount("03 March 2023", loanId, "1000"); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + // verify down-payment transaction created + checkDownPaymentTransaction(disbursementDate, 250.0f, 0.0f, 0.0f, 0.0f, loanId); + + // verify journal entries for down-payment + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", + new JournalEntry(250, JournalEntry.TransactionType.CREDIT)); + journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "03 March 2023", + new JournalEntry(250, JournalEntry.TransactionType.DEBIT)); + + // verify installment details + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(0).getDueDate()); + assertEquals(1000.0, loanDetails.getRepaymentSchedule().getPeriods().get(0).getPrincipalLoanBalanceOutstanding()); + assertEquals(1, loanDetails.getRepaymentSchedule().getPeriods().get(1).getPeriod()); + assertEquals(LocalDate.of(2023, 3, 3), loanDetails.getRepaymentSchedule().getPeriods().get(1).getDueDate()); + assertEquals(250.0, loanDetails.getRepaymentSchedule().getPeriods().get(1).getTotalInstallmentAmountForPeriod()); + assertEquals(true, loanDetails.getRepaymentSchedule().getPeriods().get(1).getDownPaymentPeriod()); + assertEquals(2, loanDetails.getRepaymentSchedule().getPeriods().get(2).getPeriod()); + assertEquals(LocalDate.of(2023, 4, 2), loanDetails.getRepaymentSchedule().getPeriods().get(2).getDueDate()); + assertEquals(750.0, loanDetails.getRepaymentSchedule().getPeriods().get(2).getTotalInstallmentAmountForPeriod()); + assertEquals(false, loanDetails.getRepaymentSchedule().getPeriods().get(2).getDownPaymentPeriod()); + + loanTransactionHelper.makeLoanRepayment((long) loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy") + .transactionDate("03 March 2023").locale("en").transactionAmount(800.0)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + // verify down-payment details for Loan + assertTrue(loanDetails.getStatus().getOverpaid()); + assertEquals(50.0, loanDetails.getTotalOverpaid()); + + // second disbursement + + disbursementDate = LocalDate.of(2023, 3, 5); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, disbursementDate); + loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "20"); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertEquals(1000.0, loanDetails.getTransactions().get(0).getAmount()); + assertEquals("loanTransactionType.downPayment", loanDetails.getTransactions().get(1).getType().getCode()); + assertEquals(250.0, loanDetails.getTransactions().get(1).getAmount()); + assertTrue(loanDetails.getTransactions().get(2).getType().getRepayment()); + assertEquals(800.0, loanDetails.getTransactions().get(2).getAmount()); + assertTrue(loanDetails.getTransactions().get(3).getType().getDisbursement()); + assertEquals(20.0, loanDetails.getTransactions().get(3).getAmount()); + assertEquals(0.0, loanDetails.getTransactions().get(3).getOutstandingLoanBalance()); + assertEquals(4, loanDetails.getTransactions().size()); + + assertTrue(loanDetails.getStatus().getOverpaid()); + assertEquals(30.0, loanDetails.getTotalOverpaid()); + + loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "30"); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(4).getType().getDisbursement()); + assertEquals(30.0, loanDetails.getTransactions().get(4).getAmount()); + assertEquals(0.0, loanDetails.getTransactions().get(4).getOutstandingLoanBalance()); + assertEquals(5, loanDetails.getTransactions().size()); + + assertTrue(loanDetails.getStatus().getClosedObligationsMet()); + assertEquals(0.0, loanDetails.getSummary().getTotalOutstanding()); + assertEquals(null, loanDetails.getTotalOverpaid()); + + PostLoansLoanIdTransactionsResponse repayment = loanTransactionHelper.makeLoanRepayment((long) loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("05 March 2023").locale("en") + .transactionAmount(1.0)); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getStatus().getOverpaid()); + assertEquals(1.0, loanDetails.getTotalOverpaid()); + + loanTransactionHelper.disburseLoanWithTransactionAmount("05 March 2023", loanId, "40"); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment()); + assertEquals(1.0, loanDetails.getTransactions().get(5).getAmount()); + assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement()); + assertEquals(40.0, loanDetails.getTransactions().get(6).getAmount()); + assertEquals(39.0, loanDetails.getTransactions().get(6).getOutstandingLoanBalance()); + assertEquals("loanTransactionType.downPayment", loanDetails.getTransactions().get(7).getType().getCode()); + assertEquals(9.0, loanDetails.getTransactions().get(7).getAmount()); + assertEquals(30.0, loanDetails.getTransactions().get(7).getOutstandingLoanBalance()); + assertEquals(8, loanDetails.getTransactions().size()); + + assertTrue(loanDetails.getStatus().getActive()); + assertEquals(30.0, loanDetails.getSummary().getTotalOutstanding()); + + loanTransactionHelper.reverseLoanTransaction(repayment.getLoanId(), repayment.getResourceId(), + new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("05 March 2023") + .transactionAmount(0.0).locale("en")); + + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment()); + assertEquals(1.0, loanDetails.getTransactions().get(5).getAmount()); + assertTrue(loanDetails.getTransactions().get(5).getManuallyReversed()); + assertTrue(loanDetails.getTransactions().get(6).getType().getDisbursement()); + assertEquals(40.0, loanDetails.getTransactions().get(6).getAmount()); + assertEquals(40.0, loanDetails.getTransactions().get(6).getOutstandingLoanBalance()); + assertEquals("loanTransactionType.downPayment", loanDetails.getTransactions().get(7).getType().getCode()); + assertEquals(9.0, loanDetails.getTransactions().get(7).getAmount()); + assertEquals(31.0, loanDetails.getTransactions().get(7).getOutstandingLoanBalance()); + assertEquals(8, loanDetails.getTransactions().size()); + + assertTrue(loanDetails.getStatus().getActive()); + assertEquals(31.0, loanDetails.getSummary().getTotalOutstanding()); + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + + private void checkNoDownPaymentTransaction(final Integer loanID) { + ArrayList transactions = (ArrayList) loanTransactionHelper.getLoanTransactions(requestSpec, responseSpec, loanID); + boolean isTransactionFound = false; + for (int i = 0; i < transactions.size(); i++) { + HashMap transactionType = (HashMap) transactions.get(i).get("type"); + boolean isDownPaymentTransaction = (Boolean) transactionType.get("downPayment"); + + if (isDownPaymentTransaction) { + isTransactionFound = true; + break; + } + } + assertFalse(isTransactionFound, "Down Payment entries are posted"); + } + + private void checkDownPaymentTransaction(final LocalDate transactionDate, final Float principalPortion, final Float interestPortion, + final Float feePortion, final Float penaltyPortion, final Integer loanID) { + ArrayList transactions = (ArrayList) loanTransactionHelper.getLoanTransactions(requestSpec, responseSpec, loanID); + boolean isTransactionFound = false; + for (int i = 0; i < transactions.size(); i++) { + HashMap transactionType = (HashMap) transactions.get(i).get("type"); + boolean isDownPaymentTransaction = (Boolean) transactionType.get("downPayment"); + + if (isDownPaymentTransaction) { + ArrayList downPaymentDateAsArray = (ArrayList) transactions.get(i).get("date"); + LocalDate downPaymentEntryDate = LocalDate.of(downPaymentDateAsArray.get(0), downPaymentDateAsArray.get(1), + downPaymentDateAsArray.get(2)); + + if (DateUtils.isEqual(transactionDate, downPaymentEntryDate)) { + isTransactionFound = true; + assertEquals(principalPortion, Float.valueOf(String.valueOf(transactions.get(i).get("principalPortion"))), + "Mismatch in transaction amounts"); + assertEquals(interestPortion, Float.valueOf(String.valueOf(transactions.get(i).get("interestPortion"))), + "Mismatch in transaction amounts"); + assertEquals(feePortion, Float.valueOf(String.valueOf(transactions.get(i).get("feeChargesPortion"))), + "Mismatch in transaction amounts"); + assertEquals(penaltyPortion, Float.valueOf(String.valueOf(transactions.get(i).get("penaltyChargesPortion"))), + "Mismatch in transaction amounts"); + break; + } + } + } + assertTrue(isTransactionFound, "No Down Payment entries are posted"); + } + + private Integer createLoanAccountMultipleRepaymentsDisbursement(final Integer clientID, final Long loanProductID, + final String externalId) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30") + .withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays() + .withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03 March 2023") + .withSubmittedOnDate("03 March 2023").withLoanType("individual").withExternalId(externalId) + .build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId, null); + return loanId; + } + + private GetLoanProductsProductIdResponse createLoanProductWithDownPaymentConfigurationAndAccrualAccounting( + LoanTransactionHelper loanTransactionHelper, Integer delinquencyBucketId, Boolean enableDownPayment, + String disbursedAmountPercentageForDownPayment, boolean enableAutoRepaymentForDownPayment, final Account... accounts) { + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() + .withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30") + .withDaysInYear("365").withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true) + .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) + .build(null); + final Integer loanProductId = loanTransactionHelper.getLoanProductId(loanProductJSON); + return loanTransactionHelper.getLoanProduct(loanProductId); + } + + private Integer createLoanAccountWithAdvancedPaymentAllocation(final Integer clientID, final Long loanProductID, + final String externalId) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("30") + .withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY) + .withLoanTermFrequencyAsDays().withNumberOfRepayments("1").withRepaymentEveryAfter("30").withRepaymentFrequencyTypeAsDays() + .withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance().withAmortizationTypeAsEqualPrincipalPayments() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withExpectedDisbursementDate("03 March 2023") + .withSubmittedOnDate("03 March 2023").withLoanType("individual").withExternalId(externalId) + .build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("03 March 2023", "1000", loanId, null); + return loanId; + } + + private GetLoanProductsProductIdResponse createProgressiveLoanProductWithDownPaymentConfigurationAndAccrualAccounting( + LoanTransactionHelper loanTransactionHelper, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment, + boolean enableAutoRepaymentForDownPayment, final Account... accounts) { + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() + .withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL).withLoanScheduleType(LoanScheduleType.PROGRESSIVE) + .addAdvancedPaymentAllocation(createDefaultPaymentAllocation()).withRepaymentAfterEvery("1").withNumberOfRepayments("1") + .withRepaymentTypeAsMonth().withinterestRatePerPeriod("0").withInterestRateFrequencyTypeAsMonths() + .withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withAccountingRulePeriodicAccrual(accounts).withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30") + .withDaysInYear("365").withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true) + .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) + .build(null); + final Integer loanProductId = loanTransactionHelper.getLoanProductId(loanProductJSON); + return loanTransactionHelper.getLoanProduct(loanProductId); + } + + private List getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) { + AtomicInteger integer = new AtomicInteger(1); + return Arrays.stream(paymentAllocationTypes).map(pat -> { + PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder(); + paymentAllocationOrder.setPaymentAllocationRule(pat.name()); + paymentAllocationOrder.setOrder(integer.getAndIncrement()); + return paymentAllocationOrder; + }).toList(); + } + + private AdvancedPaymentData createDefaultPaymentAllocation() { + AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData(); + advancedPaymentData.setTransactionType("DEFAULT"); + advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT"); + + List paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY, + PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST, + PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL, + PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE, + PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST); + + advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders); + return advancedPaymentData; + } + + private Integer createApproveAndDisburseLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) { + + String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance() + .withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod() + .withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual") + .withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null); + + final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON); + loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null); + loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanId, "1000"); + return loanId; + } + + private GetLoanProductsProductIdResponse createLoanProductWithEnableDownPaymentAndMultipleDisbursementsWithDisableRepaymentConfiguration( + LoanTransactionHelper loanTransactionHelper, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment, + boolean enableAutoRepaymentForDownPayment) { + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth() + .withRepaymentAfterEvery("1").withNumberOfRepayments("3").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeAsRepaymentPeriod(true).withDaysInMonth("30").withDaysInYear("365") + .withMoratorium("0", "0").withMultiDisburse().withDisallowExpectedDisbursements(true) + .withEnableDownPayment(enableDownPayment, disbursedAmountPercentageForDownPayment, enableAutoRepaymentForDownPayment) + .build(null); + final Integer loanProductId = loanTransactionHelper.getLoanProductId(loanProductJSON); + return loanTransactionHelper.getLoanProduct(loanProductId); + } + private Integer createLoanProductWithDownPaymentConfiguration(final LoanTransactionHelper loanTransactionHelper, final Integer delinquencyBucketId, Boolean enableDownPayment, String disbursedAmountPercentageForDownPayment, Boolean enableAutoRepaymentForDownPayment, boolean multiDisbursement) { @@ -929,4 +1609,13 @@ public class LoanRepaymentScheduleWithDownPaymentTest { loanTransactionHelper.disburseLoanWithTransactionAmount("04 September 2022", loanId, "300"); return loanId; } + + private PutLoanProductsProductIdResponse updateLoanProduct(LoanTransactionHelper loanTransactionHelper, Long id) { + // down-payment configuration + Boolean enableDownPayment = true; + BigDecimal disbursedAmountPercentageForDownPayment = BigDecimal.valueOf(25.0); + final PutLoanProductsProductIdRequest requestModifyLoan = new PutLoanProductsProductIdRequest().enableDownPayment(enableDownPayment) + .disbursedAmountPercentageForDownPayment(disbursedAmountPercentageForDownPayment).locale("en"); + return loanTransactionHelper.updateLoanProduct(id, requestModifyLoan); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoLoanDisbursalWithDownPaymentIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoLoanDisbursalWithDownPaymentIntegrationTest.java index b95b44ad5..6ecb979ac 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoLoanDisbursalWithDownPaymentIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/UndoLoanDisbursalWithDownPaymentIntegrationTest.java @@ -931,11 +931,11 @@ public class UndoLoanDisbursalWithDownPaymentIntegrationTest extends BaseLoanInt // verify transactions verifyTransactions(loanId, // - transaction(250.0, "Down Payment", "01 January 2023"), // transaction(1000.0, "Disbursement", "01 January 2023"), // + transaction(250.0, "Down Payment", "01 January 2023"), // transaction(300.0, "Repayment", "10 January 2023"), // - transaction(100.0, "Down Payment", "15 January 2023"), // - transaction(400.0, "Disbursement", "15 January 2023") // + transaction(400.0, "Disbursement", "15 January 2023"), // + transaction(100.0, "Down Payment", "15 January 2023") // ); // verify journal entries @@ -966,8 +966,8 @@ public class UndoLoanDisbursalWithDownPaymentIntegrationTest extends BaseLoanInt // verify transactions verifyTransactions(loanId, // - transaction(250.0, "Down Payment", "01 January 2023"), // transaction(1000.0, "Disbursement", "01 January 2023"), // + transaction(250.0, "Down Payment", "01 January 2023"), // transaction(300.0, "Repayment", "10 January 2023") // );