From 0a9657b774ad3c6bf5320d2f47bf5c338076ff9e Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Mon, 17 Jul 2023 18:26:20 +0200 Subject: [PATCH] FINERACT-1806: Stop recognizing income after charge-off --- .../portfolio/loanaccount/domain/Loan.java | 4 +- .../domain/LoanAccountDomainServiceJpa.java | 5 +- ...cheduleCalculationPlatformServiceImpl.java | 3 +- .../service/LoanReadPlatformServiceImpl.java | 14 +- ...WritePlatformServiceJpaRepositoryImpl.java | 5 +- .../LoanChargeOffAccountingTest.java | 166 +++++++++++++++++- 6 files changed, 177 insertions(+), 20 deletions(-) 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 dd9cd2bd1..60da8b625 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 @@ -3475,7 +3475,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private void processIncomeAccrualTransactionOnLoanClosure() { if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() - && this.getStatus().isClosedObligationsMet()) { + && this.getStatus().isClosedObligationsMet() && !isNpa() && !isChargedOff()) { ExternalId externalId = ExternalId.empty(); boolean isExternalIdAutoGenerationEnabled = TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled(); @@ -5747,7 +5747,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) { - if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { + if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff()) { return null; } final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java index 2270c22fb..807342ad9 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java @@ -584,7 +584,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { public void recalculateAccruals(Loan loan, boolean isInterestCalculationHappened) { LocalDate accruedTill = loan.getAccruedTill(); if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !isInterestCalculationHappened || accruedTill == null || loan.isNpa() - || !loan.getStatus().isActive()) { + || !loan.getStatus().isActive() || loan.isChargedOff()) { return; } @@ -889,7 +889,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService { if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() // to avoid collision with processIncomeAccrualTransactionOnLoanClosure() && !(loan.getLoanInterestRecalculationDetails() != null - && loan.getLoanInterestRecalculationDetails().isCompoundingToBePostedAsTransaction())) { + && loan.getLoanInterestRecalculationDetails().isCompoundingToBePostedAsTransaction()) + && !loan.isNpa() && !loan.isChargedOff()) { MonetaryCurrency currency = loan.getCurrency(); Money interestPortion = Money.zero(currency); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java index 9c975adcd..65d8206b5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleCalculationPlatformServiceImpl.java @@ -148,7 +148,8 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory .determineProcessor(loan.transactionProcessingStrategy()); - if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || !loan.getStatus().isActive() + if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.isNpa() || loan.isChargedOff() + || !loan.getStatus().isActive() || !loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()) { return; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 710fa04fd..ef0b1a2cf 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -1768,7 +1768,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))") .append(" or ( ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))") .append(" or ( ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))") - .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false and ls.duedate <= :currentDate) "); + .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false and loan.is_charged_off = false and ls.duedate <= :currentDate) "); if (organisationStartDate != null) { sqlBuilder.append(" and ls.duedate > :organisationStartDate "); @@ -1793,7 +1793,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .append(" and (((ls.fee_charges_amount <> COALESCE(ls.accrual_fee_charges_derived, 0))") .append(" or ( ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))") .append(" or ( ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))") - .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false) "); + .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and loan.is_npa=false and loan.is_charged_off = false) "); if (organisationStartDate != null) { sqlBuilder.append(" and ls.duedate > :organisationStartDate "); @@ -1830,7 +1830,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .append(" or (ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))") .append(" or (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))") .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or loan.closedon_date is null)") - .append(" and loan.is_npa=false and (ls.duedate <= :tillDate or (ls.duedate > :tillDate and ls.fromdate < :tillDate)") + .append(" and loan.is_npa=false and loan.is_charged_off = false and (ls.duedate <= :tillDate or (ls.duedate > :tillDate and ls.fromdate < :tillDate)") .append(" or (ls.installment = 1 and ls.fromdate = :tillDate))) "); Map paramMap = new HashMap<>(5); if (organisationStartDate != null) { @@ -1859,7 +1859,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .append(" or (ls.penalty_charges_amount <> COALESCE(ls.accrual_penalty_charges_derived, 0))") .append(" or (ls.interest_amount <> COALESCE(ls.accrual_interest_derived, 0)))") .append(" and loan.loan_status_id=:active and mpl.accounting_type=:type and (loan.closedon_date <= :tillDate or loan.closedon_date is null)") - .append(" and loan.is_npa=false)"); + .append(" and loan.is_npa=false and loan.is_charged_off = false)"); Map paramMap = new HashMap<>(5); if (organisationStartDate != null) { sqlBuilder.append(" and ls.duedate > :organisationStartDate "); @@ -2058,7 +2058,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = true"); sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); sqlBuilder.append(" WHERE ml.loan_status_id = ? "); - sqlBuilder.append(" and ml.is_npa = false and dd.is_reversed = false "); + sqlBuilder.append(" and ml.is_npa = false and loan.is_charged_off = false and dd.is_reversed = false "); sqlBuilder.append(" and (("); sqlBuilder.append("ml.interest_recalculation_enabled = true "); sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ?)"); @@ -2106,7 +2106,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa sqlBuilder.append(" left join m_floating_rates bfr on bfr.is_base_lending_rate = true"); sqlBuilder.append(" left join m_floating_rates_periods bfrp on bfr.id = bfrp.floating_rates_id and bfrp.created_date >= ?"); sqlBuilder.append(" WHERE ml.loan_status_id = ? "); - sqlBuilder.append(" and ml.is_npa = false and dd.is_reversed = false "); + sqlBuilder.append(" and ml.is_npa = false and loan.is_charged_off = false and dd.is_reversed = false "); sqlBuilder.append(" and (("); sqlBuilder.append("ml.interest_recalculation_enabled = true "); sqlBuilder.append(" and (ml.interest_recalcualated_on is null or ml.interest_recalcualated_on <> ? )"); @@ -2397,7 +2397,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa .append(" inner join m_loan_repayment_schedule as repsch on repsch.loan_id = loan.id ") .append(" inner join m_loan_interest_recalculation_additional_details as adddet on adddet.loan_repayment_schedule_id = repsch.id ") .append(" left join m_loan_transaction as trans on (trans.is_reversed <> true and trans.transaction_type_enum = 19 and trans.loan_id = loan.id and trans.transaction_date = adddet.effective_date) ") - .append(" where loan.loan_status_id = 300 ").append(" and loan.is_npa = false ") + .append(" where loan.loan_status_id = 300 ").append(" and loan.is_npa = false and loan.is_charged_off = false ") .append(" and adddet.effective_date is not null ").append(" and trans.transaction_date is null ") .append(" and adddet.effective_date < ? "); try { 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 d55b3d2a9..79eb8005c 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 @@ -2749,10 +2749,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf throw new GeneralPlatformDomainRuleException("error.msg.loan.transaction.cannot.be.a.future.date", errorMessage, transactionDate); } - if (loan.isInterestBearing()) { - throw new GeneralPlatformDomainRuleException("error.msg.loan.is.interest.bearing", - "Loan: " + loanId + " Charge-off is not allowed. Loan Account is interest bearing", loanId); - } + businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeOffPreBusinessEvent(loan)); if (command.hasParameter(LoanApiConstants.chargeOffReasonIdParamName)) { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java index 19da79d47..81462b81e 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanChargeOffAccountingTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import io.restassured.builder.RequestSpecBuilder; @@ -28,6 +29,7 @@ import io.restassured.specification.ResponseSpecification; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; +import java.util.List; import java.util.UUID; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest; @@ -35,18 +37,23 @@ import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PutLoansLoanIdResponse; +import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType; +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.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.accounting.PeriodicAccrualAccountingHelper; 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.system.CodeHelper; +import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -55,11 +62,9 @@ import org.junit.jupiter.api.extension.ExtendWith; public class LoanChargeOffAccountingTest { private ResponseSpecification responseSpec; - private ResponseSpecification responseSpec403; private RequestSpecification requestSpec; private ClientHelper clientHelper; private LoanTransactionHelper loanTransactionHelper; - private LoanTransactionHelper loanTransactionHelperValidationError; private JournalEntryHelper journalEntryHelper; private AccountHelper accountHelper; private Account assetAccount; @@ -67,16 +72,17 @@ public class LoanChargeOffAccountingTest { private Account expenseAccount; private Account overpaymentAccount; private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); + private InlineLoanCOBHelper inlineLoanCOBHelper; + private PeriodicAccrualAccountingHelper periodicAccrualAccountingHelper; @BeforeEach public void setup() { Utils.initializeRESTAssured(); this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.requestSpec.header("Fineract-Platform-TenantId", "default"); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); - this.responseSpec403 = new ResponseSpecBuilder().expectStatusCode(403).build(); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); - this.loanTransactionHelperValidationError = new LoanTransactionHelper(this.requestSpec, new ResponseSpecBuilder().build()); this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); this.assetAccount = this.accountHelper.createAssetAccount(); this.incomeAccount = this.accountHelper.createIncomeAccount(); @@ -84,6 +90,8 @@ public class LoanChargeOffAccountingTest { this.overpaymentAccount = this.accountHelper.createLiabilityAccount(); this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, this.responseSpec); this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec); + this.inlineLoanCOBHelper = new InlineLoanCOBHelper(this.requestSpec, this.responseSpec); + this.periodicAccrualAccountingHelper = new PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec); } @Test @@ -679,6 +687,108 @@ public class LoanChargeOffAccountingTest { new JournalEntry(20, JournalEntry.TransactionType.DEBIT)); } + @Test + public void noIncomeRecognitionAfterChargeOff() { + try { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.TRUE); + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 5)); + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + final Integer loanProductId = this.createLoanProductWithInterestRecalculation(assetAccount, incomeAccount, expenseAccount, + overpaymentAccount); + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + final Integer loanId = this.createLoanEntityWithEntitiesForTestResceduleWithLatePayment(clientId, loanProductId); + + // apply charges + Integer feeCharge = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false)); + + LocalDate targetDate = LocalDate.of(2022, 9, 5); + final String feeCharge1AddedDate = dateFormatter.format(targetDate); + Integer feeLoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10")); + + // apply penalty + Integer penalty = ChargesHelper.createCharges(requestSpec, responseSpec, + ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", true)); + + final String penaltyCharge1AddedDate = dateFormatter.format(targetDate); + + Integer penalty1LoanChargeId = this.loanTransactionHelper.addChargesForLoan(loanId, + LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty), penaltyCharge1AddedDate, "10")); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 6)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual()); + assertEquals(2, loanDetails.getTransactions().size()); + + // set loan as chargeoff + String randomText = Utils.randomStringGenerator("en", 5) + Utils.randomNumberGenerator(6) + + Utils.randomStringGenerator("is", 5); + Integer chargeOffReasonId = CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1); + String transactionExternalId = UUID.randomUUID().toString(); + this.loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("6 September 2020").locale("en").dateFormat("dd MMMM yyyy") + .externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId)); + loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getActive()); + assertTrue(loanDetails.getChargedOff()); + + // no accrual + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 7)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual()); + assertTrue(loanDetails.getTransactions().get(2).getType().getChargeoff()); + assertEquals(3, loanDetails.getTransactions().size()); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 8)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(dateFormatter.format(LocalDate.of(2020, 9, 8))); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual()); + assertTrue(loanDetails.getTransactions().get(2).getType().getChargeoff()); + assertEquals(3, loanDetails.getTransactions().size()); + + loanTransactionHelper.undoChargeOffLoan((long) loanId, new PostLoansLoanIdTransactionsRequest()); + // generate accrual again + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 9)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual()); + assertTrue(loanDetails.getTransactions().get(2).getType().getChargeoff()); + assertTrue(loanDetails.getTransactions().get(3).getType().getAccrual()); + assertEquals(4, loanDetails.getTransactions().size()); + + BusinessDateHelper.updateBusinessDate(requestSpec, responseSpec, BusinessDateType.BUSINESS_DATE, LocalDate.of(2020, 9, 10)); + + this.loanTransactionHelper.chargeOffLoan((long) loanId, + new PostLoansLoanIdTransactionsRequest().transactionDate("10 September 2020").locale("en").dateFormat("dd MMMM yyyy") + .chargeOffReasonId((long) chargeOffReasonId)); + + loanTransactionHelper.makeLoanRepayment(loanId.longValue(), new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy") + .transactionDate("10 September 2020").locale("en").transactionAmount(15825.23)); + inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.longValue())); + loanDetails = loanTransactionHelper.getLoanDetails(loanId.longValue()); + assertTrue(loanDetails.getTransactions().get(0).getType().getDisbursement()); + assertTrue(loanDetails.getTransactions().get(1).getType().getAccrual()); + assertTrue(loanDetails.getTransactions().get(2).getType().getChargeoff()); + assertTrue(loanDetails.getTransactions().get(3).getType().getAccrual()); + assertTrue(loanDetails.getTransactions().get(4).getType().getChargeoff()); + assertTrue(loanDetails.getTransactions().get(5).getType().getRepayment()); + assertEquals(6, loanDetails.getTransactions().size()); + } finally { + GlobalConfigurationHelper.updateIsBusinessDateEnabled(requestSpec, responseSpec, Boolean.FALSE); + } + } + private Integer createLoanAccount(final Integer clientID, final Integer loanProductID, final String externalId) { String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1") @@ -705,6 +815,34 @@ public class LoanChargeOffAccountingTest { return this.loanTransactionHelper.getLoanProductId(loanProductJSON); } + private Integer createLoanProductWithInterestRecalculation(final Account... accounts) { + final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE; + final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS; + final String recalculationRestFrequencyType = LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY; + final String recalculationRestFrequencyInterval = "0"; + final String preCloseInterestCalculationStrategy = LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE; + final String recalculationCompoundingFrequencyType = null; + final String recalculationCompoundingFrequencyInterval = null; + final Integer recalculationCompoundingFrequencyOnDayType = null; + final Integer recalculationCompoundingFrequencyDayOfWeekType = null; + final Integer recalculationRestFrequencyOnDayType = null; + final Integer recalculationRestFrequencyDayOfWeekType = null; + + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("100000.00").withNumberOfRepayments("12") + .withinterestRatePerPeriod("18").withInterestRateFrequencyTypeAsYear().withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeAsDays() + .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, + preCloseInterestCalculationStrategy) + .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyOnDayType, recalculationRestFrequencyDayOfWeekType) + .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyOnDayType, + recalculationCompoundingFrequencyDayOfWeekType) + .withAccountingRulePeriodicAccrual(accounts).build(null); + + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + private Integer createLoanProductWithCashBasedAccounting(final Account... accounts) { final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentAfterEvery("1") @@ -715,4 +853,24 @@ public class LoanChargeOffAccountingTest { return this.loanTransactionHelper.getLoanProductId(loanProductJSON); } + private Integer createLoanEntityWithEntitiesForTestResceduleWithLatePayment(Integer clientId, Integer loanProductId) { + String firstRepaymentDate = "02 September 2020"; + String submittedDate = "02 September 2020"; + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("15000").withLoanTermFrequency("12") + .withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1") + .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays() + .withInterestRatePerPeriod("12").withInterestTypeAsDecliningBalance().withSubmittedOnDate(submittedDate) + .withExpectedDisbursementDate(submittedDate).withFirstRepaymentDate(firstRepaymentDate) + .withRepaymentStrategy( + LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY) + .withinterestChargedFromDate(submittedDate).build(clientId.toString(), loanProductId.toString(), null); + + Integer loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + this.loanTransactionHelper.approveLoan(submittedDate, loanId); + this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(submittedDate, loanId, "10000.00"); + return loanId; + } + }