FINERACT-2134: Missing charge paid by references and journal entries for disbursement occuring accruals
This commit is contained in:
committed by
Arnold Gálovics
parent
39d95f393b
commit
33f666ccc3
+41
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static java.lang.System.lineSeparator;
|
||||
import static org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType.BUSINESS_DATE;
|
||||
import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
|
||||
import static org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder.DUE_PENALTY_INTEREST_PRINCIPAL_FEE_IN_ADVANCE_PENALTY_INTEREST_PRINCIPAL_FEE_STRATEGY;
|
||||
@@ -26,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
import io.restassured.builder.RequestSpecBuilder;
|
||||
import io.restassured.builder.ResponseSpecBuilder;
|
||||
@@ -39,6 +41,7 @@ import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
@@ -59,6 +62,7 @@ import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.JournalEntryTransactionItem;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostChargesResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
@@ -641,6 +645,9 @@ public abstract class BaseLoanIntegrationTest {
|
||||
loanTransactionHelper.undoLastDisbursalLoan(loanId, new PostLoansLoanIdRequest());
|
||||
}
|
||||
|
||||
// Note: this is buggy because if multiple journal entries are for the same account, amount and type, the
|
||||
// verification will pass
|
||||
// not all journal entries have been validated - since there might be duplicates
|
||||
protected void verifyJournalEntries(Long loanId, Journal... entries) {
|
||||
GetJournalEntriesTransactionIdResponse journalEntriesForLoan = journalEntryHelper.getJournalEntriesForLoan(loanId);
|
||||
Assertions.assertEquals(entries.length, journalEntriesForLoan.getPageItems().size());
|
||||
@@ -653,6 +660,27 @@ public abstract class BaseLoanIntegrationTest {
|
||||
});
|
||||
}
|
||||
|
||||
protected void verifyJournalEntriesSequentially(Long loanId, Journal... entries) {
|
||||
GetJournalEntriesTransactionIdResponse journalEntriesForLoan = journalEntryHelper.getJournalEntriesForLoan(loanId);
|
||||
List<JournalEntryTransactionItem> sortedJournalEntries = journalEntriesForLoan.getPageItems().stream()
|
||||
.sorted(Comparator.comparing(JournalEntryTransactionItem::getId)).toList();
|
||||
for (int i = 0; i < entries.length && i < journalEntriesForLoan.getPageItems().size(); i++) {
|
||||
Journal journalEntry = entries[i];
|
||||
JournalEntryTransactionItem item = sortedJournalEntries.get(i);
|
||||
boolean found = Objects.equals(item.getAmount(), journalEntry.amount)
|
||||
&& Objects.equals(item.getGlAccountId(), journalEntry.account.getAccountID().longValue())
|
||||
&& Objects.requireNonNull(item.getEntryType()).getValue().equals(journalEntry.type);
|
||||
assertTrue(found, "Journal entry mismatch at position " + i + "." + lineSeparator() + "Wanted Journal entry: " + journalEntry
|
||||
+ lineSeparator() + "Actual Journal entry: " + item);
|
||||
}
|
||||
if (journalEntriesForLoan.getPageItems().size() > entries.length) {
|
||||
fail("Some Journal Entries are not verified. The missing entries are here: "
|
||||
+ sortedJournalEntries.subList(entries.length, sortedJournalEntries.size()));
|
||||
}
|
||||
Assertions.assertEquals(entries.length, journalEntriesForLoan.getPageItems().size(),
|
||||
"There were more journal entries expected than actually present.");
|
||||
}
|
||||
|
||||
protected void verifyTRJournalEntries(Long transactionId, Journal... entries) {
|
||||
GetJournalEntriesTransactionIdResponse journalEntriesForLoan = journalEntryHelper.getJournalEntries("L" + transactionId.toString());
|
||||
Assertions.assertEquals(entries.length, journalEntriesForLoan.getPageItems().size());
|
||||
@@ -675,6 +703,13 @@ public abstract class BaseLoanIntegrationTest {
|
||||
return loanChargeId.longValue();
|
||||
}
|
||||
|
||||
protected Long createDisbursementPercentageCharge(double percentageAmount) {
|
||||
Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, ChargesHelper
|
||||
.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT, String.valueOf(percentageAmount)));
|
||||
assertNotNull(chargeId);
|
||||
return chargeId.longValue();
|
||||
}
|
||||
|
||||
protected void verifyRepaymentSchedule(Long loanId, Installment... installments) {
|
||||
GetLoansLoanIdResponse loanResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());
|
||||
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||
@@ -1192,6 +1227,11 @@ public abstract class BaseLoanIntegrationTest {
|
||||
public static final Integer FLAT = 1;
|
||||
}
|
||||
|
||||
public static class InterestRecalculationCompoundingMethod {
|
||||
|
||||
public static final Integer NONE = 0;
|
||||
}
|
||||
|
||||
public static class RepaymentFrequencyType {
|
||||
|
||||
public static final Integer MONTHS = 2;
|
||||
@@ -1227,6 +1267,7 @@ public abstract class BaseLoanIntegrationTest {
|
||||
|
||||
public static class RescheduleStrategyMethod {
|
||||
|
||||
public static final Integer REDUCE_EMI_AMOUNT = 3;
|
||||
public static final Integer ADJUST_LAST_UNPAID_PERIOD = 4;
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -18,6 +18,9 @@
|
||||
*/
|
||||
package org.apache.fineract.integrationtests.common.accounting;
|
||||
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
public class Account {
|
||||
|
||||
public enum AccountType {
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.loan.repayment;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.client.models.ChargeData;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansRequestChargeData;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanRepaymentTest extends BaseLoanIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void test_LoanRepaymentWorks_WhenDisbursementChargeIsAvailable_AndAccrualAccounting_AndDailyRecalculateInterest_AndDailyInterestCalculationPeriod() {
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
// Create charges
|
||||
double charge1Amount = 1.0;
|
||||
double charge2Amount = 1.5;
|
||||
Long charge1Id = createDisbursementPercentageCharge(charge1Amount);
|
||||
Long charge2Id = createDisbursementPercentageCharge(charge2Amount);
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct() //
|
||||
.numberOfRepayments(numberOfRepayments) //
|
||||
.repaymentEvery(repaymentEvery) //
|
||||
.installmentAmountInMultiplesOf(null) //
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()) //
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD)//
|
||||
.isInterestRecalculationEnabled(true)//
|
||||
.recalculationRestFrequencyInterval(1)//
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)//
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.disallowExpectedDisbursements(false)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(false)//
|
||||
.overAppliedNumber(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.multiDisburseLoan(null)//
|
||||
.charges(List.of(new ChargeData().id(charge1Id), new ChargeData().id(charge2Id)));//
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
double amount = 1000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", amount, numberOfRepayments)//
|
||||
.repaymentEvery(repaymentEvery)//
|
||||
.loanTermFrequency(numberOfRepayments)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.interestType(InterestType.DECLINING_BALANCE)//
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)//
|
||||
.charges(List.of(//
|
||||
new PostLoansRequestChargeData().chargeId(charge1Id).amount(BigDecimal.valueOf(charge1Amount)), //
|
||||
new PostLoansRequestChargeData().chargeId(charge2Id).amount(BigDecimal.valueOf(charge2Amount))//
|
||||
));//
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 January 2023"));
|
||||
|
||||
Long loanId = approvedLoanResult.getLoanId();
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1000.0), "01 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1000.0, "Disbursement", "01 January 2023"), //
|
||||
transaction(25.0, "Repayment (at time of disbursement)", "01 January 2023"), //
|
||||
transaction(10.0, "Accrual", "01 January 2023"), //
|
||||
transaction(15.0, "Accrual", "01 January 2023") //
|
||||
);
|
||||
|
||||
// verify journal entries
|
||||
verifyJournalEntries(loanId, //
|
||||
journalEntry(1000.0, loansReceivableAccount, "DEBIT"), //
|
||||
journalEntry(1000.0, fundSource, "CREDIT"), //
|
||||
journalEntry(25.0, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(25.0, fundSource, "DEBIT"), //
|
||||
journalEntry(10.0, feeReceivableAccount, "DEBIT"), //
|
||||
journalEntry(10.0, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(15.0, feeReceivableAccount, "DEBIT"), //
|
||||
journalEntry(15.0, feeIncomeAccount, "CREDIT") //
|
||||
);
|
||||
|
||||
// repay 500
|
||||
addRepaymentForLoan(loanId, 500.0, "01 January 2023");
|
||||
|
||||
// verify transactions
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1000.0, "Disbursement", "01 January 2023"), //
|
||||
transaction(25.0, "Repayment (at time of disbursement)", "01 January 2023"), //
|
||||
transaction(500.0, "Repayment", "01 January 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user