Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTer
scheduleParams.incrementPeriodNumber();
}

if (chargesDueAtTimeOfDisbursement.compareTo(BigDecimal.ZERO) > 0) {
scheduleParams.addTotalRepaymentExpected(Money.of(currency, chargesDueAtTimeOfDisbursement, mc));
}
Comment thread
Wiki05 marked this conversation as resolved.

if (loanApplicationTerms.isMultiDisburseLoan()) {
processDisbursements(loanApplicationTerms, disbursementDataList, scheduleParams, interestScheduleModel, periods,
chargesDueAtTimeOfDisbursement, true, mc);
Expand Down Expand Up @@ -317,6 +321,8 @@ private void processDisbursements(final LoanApplicationTerms loanApplicationTerm
.disbursement(disbursementData.disbursementDate(), disbursedAmount, chargesDueAtTimeOfDisbursement);
periods.add(disbursementPeriod);

scheduleParams.addTotalRepaymentExpected(Money.of(loanApplicationTerms.getCurrency(), chargesDueAtTimeOfDisbursement, mc));

// validation check for amount not exceeds specified max
// amount as per the configuration
if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getMaxOutstandingBalance() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class LoanScheduleGeneratorTest {
private static final CurrencyData CURRENCY = APPLICATION_CURRENCY.toData();
private static final BigDecimal DISBURSEMENT_AMOUNT = BigDecimal.valueOf(192.22);
private static final BigDecimal DISBURSEMENT_AMOUNT_100 = BigDecimal.valueOf(100);
private static final BigDecimal DISBURSEMENT_CHARGE = BigDecimal.valueOf(10.0);
private static final BigDecimal NOMINAL_INTEREST_RATE = BigDecimal.valueOf(9.99);
private static final int NUMBER_OF_REPAYMENTS = 6;
private static final int REPAYMENT_FREQUENCY = 1;
Expand Down Expand Up @@ -168,4 +169,36 @@ private void checkDownPaymentPeriod(LoanSchedulePlanDownPaymentPeriod period, in
assertEquals(0, expectedTotalDue.compareTo(period.getTotalDueAmount()));
assertEquals(0, expectedOutstandingLoanBalance.compareTo(period.getOutstandingLoanBalance()));
}

@Test
void testGenerateLoanScheduleWithDisbursementCharges() {
LoanRepaymentScheduleModelData modelData = new LoanRepaymentScheduleModelData(LocalDate.of(2024, 1, 1), CURRENCY,
DISBURSEMENT_AMOUNT, DISBURSEMENT_DATE, NUMBER_OF_REPAYMENTS, REPAYMENT_FREQUENCY, REPAYMENT_FREQUENCY_TYPE,
NOMINAL_INTEREST_RATE, false, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null, false, null,
InterestMethod.DECLINING_BALANCE, true, false);

ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
interestScheduleModelRepositoryWrapperMock);
generator.setLoanTransactionProcessingService(loanTransactionProcessingService);

LoanSchedulePlan loanSchedule = generator.generate(mc, modelData);

BigDecimal totalPrincipal = BigDecimal.ZERO;
BigDecimal totalInterest = BigDecimal.ZERO;
BigDecimal totalDueAmount = BigDecimal.ZERO;

for (var period : loanSchedule.getPeriods()) {
if (period instanceof LoanSchedulePlanRepaymentPeriod repayment) {
totalPrincipal = totalPrincipal.add(repayment.getPrincipalAmount());
totalInterest = totalInterest.add(repayment.getInterestAmount());
totalDueAmount = totalDueAmount.add(repayment.getTotalDueAmount());
}
}

BigDecimal expected = totalPrincipal.add(totalInterest).stripTrailingZeros();
BigDecimal actual = totalDueAmount.stripTrailingZeros();
assertEquals(expected, actual, "Total due amount including charges does not match!");
}

}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ org.gradle.parallel=true
org.gradle.daemon.idletimeout=10800000
# Temporarily disabled until we fix configuration cache compatibility
#org.gradle.configuration-cache=true
org.gradle.vfs.watch=true
org.gradle.vfs.watch=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.apache.fineract.integrationtests;

import java.math.BigDecimal;
import java.util.HashMap;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class ProgressiveLoanDisbursementChargeTest extends BaseLoanIntegrationTest {

@Test
public void testDisbursementChargeLifecycle() {
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());

runAt("1 January 2024", () -> {
final HashMap<String, Object> chargeMap = new HashMap<>();
chargeMap.put("name", Utils.uniqueRandomStringGenerator("Fee_", 6));
chargeMap.put("amount", 10.0);
chargeMap.put("currencyCode", "USD");
chargeMap.put("chargeTimeType", 1);
chargeMap.put("chargeAppliesTo", 1);
chargeMap.put("chargeCalculationType", 1);
chargeMap.put("penalty", false);
chargeMap.put("active", true);
chargeMap.put("locale", "en");
chargeMap.put("monthDayFormat", "dd MMMM");

final Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec, Utils.convertToJson(chargeMap));

PostLoanProductsResponse loanProduct = loanProductHelper
.createLoanProduct(create4IProgressive().principal(100.0).numberOfRepayments(6).interestRatePerPeriod(7.0));

Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2024", 100.0, 7.0, 6,
request -> {});

final HashMap<String, Object> loanChargeMap = new HashMap<>();
loanChargeMap.put("chargeId", chargeId);
loanChargeMap.put("amount", 10.0);
loanChargeMap.put("dueDate", "1 January 2024");
loanChargeMap.put("dateFormat", "dd MMMM yyyy");
loanChargeMap.put("locale", "en");

Utils.performServerPost(requestSpec, responseSpec, "/loans/" + loanId + "/charges?tenantIdentifier=default",
Utils.convertToJson(loanChargeMap), null);

disburseLoan(loanId, BigDecimal.valueOf(100.0), "1 January 2024");

final GetLoansLoanIdResponse loanDetails = this.loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId.intValue());

boolean feeExistsInPeriods = loanDetails.getRepaymentSchedule().getPeriods().stream()
.anyMatch(period -> period.getFeeChargesDue() != null && period.getFeeChargesDue().doubleValue() > 0);

Assertions.assertTrue(feeExistsInPeriods,
"Adam's Requirement: Disbursement charge must be present in the repayment schedule periods!");
});
}
}
Loading