diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java index 0dded0e2b..2f5e1619b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/accountdetails/service/AccountDetailsReadPlatformServiceJpaRepositoryImpl.java @@ -470,7 +470,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou .append(" l.closedon_date as closedOnDate,") .append(" cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname,") .append(" la.overdue_since_date_derived as overdueSinceDate, ") - .append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate, ") + .append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate, ") .append(" l.charged_off_on_date as chargedOffOnDate, cobu.username as chargedOffByUsername, ") .append(" cobu.firstname as chargedOffByFirstname, cobu.lastname as chargedOffByLastname ").append(" from m_loan l ") .append("LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id") @@ -541,6 +541,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate"); final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); + final LocalDate actualMaturityDate = JdbcSupport.getLocalDate(rs, "actualMaturityDate"); final LocalDate overdueSinceDate = JdbcSupport.getLocalDate(rs, "overdueSinceDate"); @@ -556,8 +557,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname, - expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, chargedOffOnDate, - chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); + actualMaturityDate, expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, + chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); return new LoanAccountSummaryData(id, accountNo, parentAccountNumber, externalId, productId, loanProductName, shortLoanProductName, loanStatus, loanType, loanCycle, timeline, inArrears, originalLoan, loanBalance, amountPaid); @@ -584,7 +585,7 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou .append(" l.approvedon_date as approvedOnDate,") .append(" l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate,") .append(" l.closedon_date as closedOnDate,").append(" la.overdue_since_date_derived as overdueSinceDate,") - .append(" l.writtenoffon_date as writtenOffOnDate, l.expected_maturedon_date as expectedMaturityDate,") + .append(" l.writtenoffon_date as writtenOffOnDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate,") .append(" g.is_active as isActive,").append(" cv.code_value as relationship,").append(" sa.on_hold_funds_derived") .append(" from m_loan l ").append(" join m_guarantor as g on g.loan_id = l.id ") .append(" join m_client as c on c.id = g.entity_id ").append(" LEFT JOIN m_product_loan AS lp ON lp.id = l.product_id") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java index f792fc278..ed750ab63 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java @@ -245,6 +245,8 @@ final class LoansApiResourceSwagger { public String disbursedByLastname; @Schema(example = "[2012, 4, 10]") public LocalDate expectedMaturityDate; + @Schema(example = "[2012, 4, 10]") + public LocalDate actualMaturityDate; @Schema(example = "[2012, 4, 3]") public LocalDate closedOnDate; @Schema(example = "[2012, 4, 10]") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java index 3c960d174..52b36d52d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanApplicationTimelineData.java @@ -56,6 +56,8 @@ public class LoanApplicationTimelineData { private final String closedByUsername; private final String closedByFirstname; private final String closedByLastname; + + private final LocalDate actualMaturityDate; private final LocalDate expectedMaturityDate; private final LocalDate writeOffOnDate; private final String writeOffByUsername; @@ -102,14 +104,15 @@ public class LoanApplicationTimelineData { final String chargedOffByUsername = null; final String chargedOffByFirstname = null; final String chargedOffByLastname = null; + final LocalDate actualMaturityDate = null; return new LoanApplicationTimelineData(submittedOnDate, submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername, rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, disbursedByFirstname, disbursedByLastname, - closedOnDate, closedByUsername, closedByFirstname, closedByLastname, expectedMaturityDate, writeOffOnDate, - writeOffByUsername, writeOffByFirstname, writeOffByLastname, chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname, - chargedOffByLastname); + closedOnDate, closedByUsername, closedByFirstname, closedByLastname, actualMaturityDate, expectedMaturityDate, + writeOffOnDate, writeOffByUsername, writeOffByFirstname, writeOffByLastname, chargedOffOnDate, chargedOffByUsername, + chargedOffByFirstname, chargedOffByLastname); } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData(final CurrencyData currency, final BigDecimal principal, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index d66428e6b..9e589df8b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -7027,4 +7027,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { return getLoanTransactions().stream().filter(predicate).toList(); } + public void handleMaturityDateActivate() { + if (this.expectedMaturityDate != null && this.actualMaturityDate == null) { + this.actualMaturityDate = this.expectedMaturityDate; + } + } + } 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 0cf51c28e..0e5acbb8b 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 @@ -616,7 +616,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.approvedon_date as approvedOnDate, abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname," + " l.expected_disbursedon_date as expectedDisbursementDate, l.disbursedon_date as actualDisbursementDate, dbu.username as disbursedByUsername, dbu.firstname as disbursedByFirstname, dbu.lastname as disbursedByLastname," + " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, " - + " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.expected_maturedon_date as expectedMaturityDate, " + + " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.maturedon_date as actualMaturityDate, l.expected_maturedon_date as expectedMaturityDate, " + " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.net_disbursal_amount as netDisbursalAmount, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery," + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing," + " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, " @@ -791,6 +791,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final LocalDate writtenOffOnDate = JdbcSupport.getLocalDate(rs, "writtenOffOnDate"); final Long writeoffReasonId = JdbcSupport.getLong(rs, "writeoffReasonId"); final String writeoffReason = rs.getString("writeoffReason"); + final LocalDate actualMaturityDate = JdbcSupport.getLocalDate(rs, "actualMaturityDate"); final LocalDate expectedMaturityDate = JdbcSupport.getLocalDate(rs, "expectedMaturityDate"); final Boolean isvariableInstallmentsAllowed = rs.getBoolean("isvariableInstallmentsAllowed"); @@ -810,8 +811,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname, withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, expectedDisbursementDate, actualDisbursementDate, disbursedByUsername, disbursedByFirstname, disbursedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname, - expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, chargedOffOnDate, - chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); + actualMaturityDate, expectedMaturityDate, writtenOffOnDate, closedByUsername, closedByFirstname, closedByLastname, + chargedOffOnDate, chargedOffByUsername, chargedOffByFirstname, chargedOffByLastname); final BigDecimal principal = rs.getBigDecimal("principal"); final BigDecimal approvedPrincipal = rs.getBigDecimal("approvedPrincipal"); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java index 3f620fc38..d34c98f8b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanStatusChangePlatformServiceImpl.java @@ -50,6 +50,9 @@ public class LoanStatusChangePlatformServiceImpl implements LoanStatusChangePlat log.debug("Loan Status {} for loan {}", loan.getStatus().getCode(), loan.getId()); loan.applyIncomeAccrualTransaction(loan.getClosedOnDate()); } + if (loan.isOpen()) { + loan.handleMaturityDateActivate(); + } } } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java index 4745dee28..1ea60fb92 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java @@ -7004,6 +7004,64 @@ public class ClientLoanIntegrationTest { assertEquals("Administrator", loanDetails.getTimeline().getChargedOffByLastname()); } + @Test + public void testCloseOpenMaturityDate() { + GlobalConfigurationHelper.updateIsAutomaticExternalIdGenerationEnabled(this.requestSpec, this.responseSpec, true); + final Account assetAccount = this.accountHelper.createAssetAccount(); + final Account incomeAccount = this.accountHelper.createIncomeAccount(); + final Account expenseAccount = this.accountHelper.createExpenseAccount(); + final Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, incomeAccount, expenseAccount, + overpaymentAccount); + + final Integer clientID = ClientHelper.createClient(requestSpec, responseSpec, "01 January 2011"); + + final Integer loanID = applyForLoanApplication(clientID, loanProductID); + + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID); + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 September 2022", loanID); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 2022", loanID, "1000"); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID); + LocalDate expectedMaturityDate = loanDetails.getTimeline().getExpectedMaturityDate(); + LocalDate actualMaturityDate = loanDetails.getTimeline().getActualMaturityDate(); + + assertTrue(expectedMaturityDate.isEqual(actualMaturityDate)); + + this.loanTransactionHelper.makeRepayment("04 September 2022", Float.parseFloat("500"), loanID); + this.loanTransactionHelper.makeRepayment("05 September 2022", Float.parseFloat("700"), loanID); + + loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID); + + expectedMaturityDate = loanDetails.getTimeline().getExpectedMaturityDate(); + actualMaturityDate = loanDetails.getTimeline().getActualMaturityDate(); + + assertNotNull(expectedMaturityDate); + assertNull(actualMaturityDate); + + this.loanTransactionHelper.reverseLoanTransaction((long) loanID, loanDetails.getTransactions().get(1).getId(), + new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat("dd MMMM yyyy").transactionDate("04 September 2022") + .transactionAmount(0.0).locale("en")); + + loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanID); + + expectedMaturityDate = loanDetails.getTimeline().getExpectedMaturityDate(); + actualMaturityDate = loanDetails.getTimeline().getActualMaturityDate(); + + assertNotNull(expectedMaturityDate); + assertNotNull(actualMaturityDate); + + assertTrue(expectedMaturityDate.isEqual(actualMaturityDate)); + + } + @Test public void testReverseReplay() { try {