diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java index b5f3aefe4..880932828 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java @@ -201,8 +201,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom { this.externalId = externalId; } - private void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount, - Integer numberOfRepayments, BigDecimal loanCharge) { + public void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount, Integer numberOfRepayments, + BigDecimal loanCharge) { switch (ChargeCalculationType.fromInt(this.chargeCalculation)) { case INVALID: diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java index 1cffa8bc5..03f9b195d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanScheduleModelDisbursementPeriod.java @@ -36,7 +36,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo private final Integer periodNumber; private final LocalDate disbursementDate; private final Money principalDisbursed; - private final BigDecimal chargesDueAtTimeOfDisbursement; + private BigDecimal chargesDueAtTimeOfDisbursement; private boolean isEMIFixedSpecificToInstallment = false; public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed, @@ -105,7 +105,7 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo @Override public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) { - return; + this.chargesDueAtTimeOfDisbursement = this.chargesDueAtTimeOfDisbursement.add(feeCharge); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 07db84d81..54003773d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -35,6 +35,7 @@ import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -110,6 +111,8 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplica import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelDisbursementPeriod; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; import org.apache.fineract.portfolio.loanaccount.serialization.VariableLoanScheduleFromApiJsonValidator; @@ -719,7 +722,9 @@ public class LoanScheduleAssembler { final List holidays, final WorkingDays workingDays, final JsonElement element, List disbursementDetails) { - final Set loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails); + Set loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails); + final Set nonCompoundingCharges = validateDisbursementPercentageCharges(loanCharges); + loanCharges.removeAll(nonCompoundingCharges); final MathContext mc = MoneyHelper.getMathContext(); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); @@ -742,7 +747,11 @@ public class LoanScheduleAssembler { loanApplicationTerms.getInterestMethod()); } - return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO); + LoanScheduleModel loanScheduleModel = loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO); + if (!nonCompoundingCharges.isEmpty()) { + updateDisbursementWithCharges(loanScheduleModel.getPeriods(), nonCompoundingCharges); + } + return loanScheduleModel; } public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, @@ -1542,4 +1551,31 @@ public class LoanScheduleAssembler { return Pair.of(loan, actualChanges); } + private Set validateDisbursementPercentageCharges(final Set loanCharges) { + Set interestCharges = new HashSet<>(); + if (loanCharges != null) { + for (final LoanCharge loanCharge : loanCharges) { + if (loanCharge.isDisbursementCharge() && (loanCharge.getChargeCalculation().isPercentageOfInterest() + || loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) { + interestCharges.add(loanCharge); + } + } + } + return interestCharges; + } + + private void updateDisbursementWithCharges(final Collection periods, + final Set nonCompoundingCharges) { + final BigDecimal totalInterest = periods.stream().filter(p -> p.isRepaymentPeriod()).map(LoanScheduleModelPeriod::interestDue) + .reduce(BigDecimal.ZERO, BigDecimal::add); + for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) { + if (loanScheduleModelPeriod instanceof LoanScheduleModelDisbursementPeriod) { + for (final LoanCharge loanCharge : nonCompoundingCharges) { + loanCharge.populateDerivedFields(totalInterest, loanCharge.amountOrPercentage(), null, BigDecimal.ZERO); + loanScheduleModelPeriod.addLoanCharges(loanCharge.getAmountOutstanding(), BigDecimal.ZERO); + } + } + } + } + } 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 d58bf9f0d..9518aea7f 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 @@ -1047,6 +1047,44 @@ public class ClientLoanIntegrationTest extends BaseLoanIntegrationTest { } + @Test + public void testLoanCharges_DISBURSEMENT_WITH_INTEREST() { + + Calendar fourMonthsfromNowCalendar = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + fourMonthsfromNowCalendar.add(Calendar.MONTH, -4); + if (fourMonthsfromNowCalendar.get(Calendar.DAY_OF_MONTH) > 27) { + fourMonthsfromNowCalendar.add(Calendar.DAY_OF_MONTH, 4); + } + + String fourMonthsfromNow = Utils.convertDateToURLFormat(fourMonthsfromNowCalendar); + final Integer clientID = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC); + ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientID); + final Integer loanProductID = createLoanProduct(false, NONE); + + List charges = new ArrayList<>(); + Integer disbursementFee = ChargesHelper.createCharges(REQUEST_SPEC, RESPONSE_SPEC, + ChargesHelper.getLoanDisbursementJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_INTEREST, "5")); + addCharges(charges, disbursementFee, "5", null); + + List collaterals = new ArrayList<>(); + final Integer loanID = applyForLoanApplicationWithPaymentStrategyAndPastMonth(clientID, loanProductID, charges, null, "1000", + LoanApplicationTestBuilder.DEFAULT_STRATEGY, fourMonthsfromNow, collaterals); + Assertions.assertNotNull(loanID); + + LOAN_TRANSACTION_HELPER.approveLoan(fourMonthsfromNow, loanID); + + String loanDetails = LOAN_TRANSACTION_HELPER.getLoanDetails(REQUEST_SPEC, RESPONSE_SPEC, loanID); + LOAN_TRANSACTION_HELPER.disburseLoanWithNetDisbursalAmount(fourMonthsfromNow, loanID, + JsonPath.from(loanDetails).get("netDisbursalAmount").toString()); + + // check for disbursement fee: Principal 1,000 with 24% Annual Rate for 6 Months we have Total Interest of: + // 120.00 + ArrayList loanSchedule = LOAN_TRANSACTION_HELPER.getLoanRepaymentSchedule(REQUEST_SPEC, RESPONSE_SPEC, loanID); + HashMap disbursementDetail = loanSchedule.get(0); + // Disbursement Fee: 5% of 120.00 = 6.00 + validateNumberForEqual("6.00", String.valueOf(disbursementDetail.get("feeChargesDue"))); + } + @Test public void testLoanCharges_DISBURSEMENT_WITH_TRANCHES() { final Integer clientID = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC);