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 2922a4f5b..e077c3aac 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 @@ -2166,9 +2166,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { final LoanStatus statusEnum = loanLifecycleStateMachine.dryTransition(LoanEvent.LOAN_REJECTED, this); if (!statusEnum.hasStateOf(LoanStatus.fromInt(this.loanStatus))) { - loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, this); - actualChanges.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); - final LocalDate rejectedOn = command.localDateValueOfParameterNamed(REJECTED_ON_DATE); final Locale locale = new Locale(command.locale()); @@ -2179,6 +2176,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { this.closedOnDate = rejectedOn; this.closedBy = currentUser; + loanLifecycleStateMachine.transition(LoanEvent.LOAN_REJECTED, this); + actualChanges.put(PARAM_STATUS, LoanEnumerations.status(this.loanStatus)); + actualChanges.put(LOCALE, command.locale()); actualChanges.put(DATE_FORMAT, command.dateFormat()); actualChanges.put(REJECTED_ON_DATE, rejectedOn.format(fmt)); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationRejectionForLoanProductWithPeriodicAccrualAccountingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationRejectionForLoanProductWithPeriodicAccrualAccountingTest.java new file mode 100644 index 000000000..e60af672c --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanApplicationRejectionForLoanProductWithPeriodicAccrualAccountingTest.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.restassured.builder.RequestSpecBuilder; +import io.restassured.builder.ResponseSpecBuilder; +import io.restassured.http.ContentType; +import io.restassured.specification.RequestSpecification; +import io.restassured.specification.ResponseSpecification; +import java.util.UUID; +import org.apache.fineract.client.models.GetLoansLoanIdResponse; +import org.apache.fineract.client.models.PostLoansLoanIdRequest; +import org.apache.fineract.client.models.PostLoansLoanIdResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +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.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class LoanApplicationRejectionForLoanProductWithPeriodicAccrualAccountingTest { + + private ResponseSpecification responseSpec; + private RequestSpecification requestSpec; + private ClientHelper clientHelper; + private LoanTransactionHelper loanTransactionHelper; + private AccountHelper accountHelper; + + @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); + this.accountHelper = new AccountHelper(this.requestSpec, this.responseSpec); + } + + @Test + public void loanApplicationRejectionForPeriodicAccrualAccountingLoanProductTest() { + + Account assetAccount = this.accountHelper.createAssetAccount(); + Account incomeAccount = this.accountHelper.createIncomeAccount(); + Account expenseAccount = this.accountHelper.createExpenseAccount(); + Account overpaymentAccount = this.accountHelper.createLiabilityAccount(); + + // Create Loan Product with Periodic Accrual accounting + final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting(assetAccount, incomeAccount, expenseAccount, + overpaymentAccount); + + // Loan ExternalId + String loanExternalIdStr = UUID.randomUUID().toString(); + + // Client and Loan account creation + final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue(); + + final Integer loanId = createLoanAccount(clientId, loanProductID, loanExternalIdStr); + + // verify Loan status as submitted and pending approval + GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoanDetails((long) loanId); + assertTrue(loanDetails.getStatus().getPendingApproval()); + + // Reject Loan application + PostLoansLoanIdResponse result = this.loanTransactionHelper.rejectLoan(loanExternalIdStr, + new PostLoansLoanIdRequest().rejectedOnDate("3 September 2022").locale("en").dateFormat("dd MMMM yyyy")); + + // Verify Loan application status is Rejected + assertTrue(result.getChanges().getStatus().getValue().equals("Rejected")); + + } + + private Integer createLoanProductWithPeriodicAccrualAccounting(final Account... accounts) { + + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("1000").withRepaymentAfterEvery("1") + .withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0") + .withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat() + .withAccountingRulePeriodicAccrual(accounts).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0") + .build(null); + + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + private Integer createLoanAccount(final Integer clientID, final Integer 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); + return loanId; + } + +}