FINERACT-2326: Capitalized Income transaction template considering over applied amount when enabled
This commit is contained in:
committed by
Adam Saghy
parent
59c98b7329
commit
54f055d9c7
+52
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* 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.portfolio.loanaccount.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public enum LoanOverAppliedCalculationType {
|
||||
|
||||
FLAT("flat"), //
|
||||
PERCENTAGE("percentage"), //
|
||||
;
|
||||
|
||||
private final String humanReadableName;
|
||||
|
||||
public static List<EnumOptionData> getValuesAsEnumOptionDataList() {
|
||||
return Arrays.stream(values()).map(v -> new EnumOptionData((long) (v.ordinal() + 1), v.name(), v.getHumanReadableName())).toList();
|
||||
}
|
||||
|
||||
public EnumOptionData asEnumOptionData() {
|
||||
return new EnumOptionData((long) this.ordinal(), this.name(), this.humanReadableName);
|
||||
}
|
||||
|
||||
public boolean isFlat() {
|
||||
return this == FLAT;
|
||||
}
|
||||
|
||||
public boolean isPercentage() {
|
||||
return this == PERCENTAGE;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanOverAppliedCalculationType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public final class LoanMaximumAmountCalculator {
|
||||
|
||||
public BigDecimal getOverAppliedMax(Loan loan) {
|
||||
final LoanProduct loanProduct = loan.getLoanProduct();
|
||||
if (LoanOverAppliedCalculationType.valueOf(loanProduct.getOverAppliedCalculationType().toUpperCase()).isPercentage()) {
|
||||
BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
|
||||
BigDecimal totalPercentage = BigDecimal.valueOf(1).add(overAppliedNumber.divide(BigDecimal.valueOf(100)));
|
||||
return loan.getProposedPrincipal().multiply(totalPercentage);
|
||||
} else {
|
||||
return loan.getProposedPrincipal().add(BigDecimal.valueOf(loanProduct.getOverAppliedNumber()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-15
@@ -43,7 +43,6 @@ import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
|
||||
import org.apache.fineract.portfolio.common.service.Validator;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
@@ -61,7 +60,6 @@ import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProces
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@@ -73,6 +71,7 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
private final LoanBuyDownFeeBalanceRepository loanBuydownFeeBalanceRepository;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final LoanMaximumAmountCalculator loanMaximumAmountCalculator;
|
||||
|
||||
@Override
|
||||
public void validateCapitalizedIncome(final JsonCommand command, final Long loanId) {
|
||||
@@ -137,7 +136,7 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
|
||||
final BigDecimal newTotal = totalDisbursed.add(capitalizedIncome).add(transactionAmount);
|
||||
|
||||
if (loan.loanProduct().isAllowApprovedDisbursedAmountsOverApplied()) {
|
||||
final BigDecimal maxAppliedAmount = getOverAppliedMax(loan);
|
||||
final BigDecimal maxAppliedAmount = loanMaximumAmountCalculator.getOverAppliedMax(loan);
|
||||
if (newTotal.compareTo(maxAppliedAmount) > 0) {
|
||||
baseDataValidator.reset().parameter("transactionAmount").failWithCode("exceeds.approved.amount",
|
||||
"Sum of disbursed amount and capitalized income can't be greater than maximum applied loan amount calculation.");
|
||||
@@ -595,16 +594,4 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
|
||||
return new HashSet<>(
|
||||
Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
|
||||
}
|
||||
|
||||
private BigDecimal getOverAppliedMax(final Loan loan) {
|
||||
final LoanProduct loanProduct = loan.getLoanProduct();
|
||||
if ("percentage".equals(loanProduct.getOverAppliedCalculationType())) {
|
||||
final BigDecimal overAppliedNumber = BigDecimal.valueOf(loanProduct.getOverAppliedNumber());
|
||||
final BigDecimal totalPercentage = BigDecimal.valueOf(1)
|
||||
.add(overAppliedNumber.divide(BigDecimal.valueOf(100L), MoneyHelper.getMathContext()));
|
||||
return loan.getProposedPrincipal().multiply(totalPercentage);
|
||||
} else {
|
||||
return loan.getProposedPrincipal().add(BigDecimal.valueOf(loanProduct.getOverAppliedNumber()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -36,6 +36,7 @@ import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeWriteP
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanMaximumAmountCalculator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanTransactionValidatorImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
||||
@@ -68,9 +69,11 @@ public class ProgressiveLoanAccountConfiguration {
|
||||
public ProgressiveLoanTransactionValidator progressiveLoanTransactionValidator(FromJsonHelper fromApiJsonHelper,
|
||||
LoanTransactionValidator loanTransactionValidator, LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
|
||||
LoanBuyDownFeeBalanceRepository loanBuydownFeeBalanceRepository, LoanTransactionRepository loanTransactionRepository) {
|
||||
LoanBuyDownFeeBalanceRepository loanBuydownFeeBalanceRepository, LoanTransactionRepository loanTransactionRepository,
|
||||
LoanMaximumAmountCalculator loanMaximumAmountCalculator) {
|
||||
return new ProgressiveLoanTransactionValidatorImpl(fromApiJsonHelper, loanTransactionValidator, loanRepositoryWrapper,
|
||||
loanCapitalizedIncomeBalanceRepository, loanBuydownFeeBalanceRepository, loanTransactionRepository);
|
||||
loanCapitalizedIncomeBalanceRepository, loanBuydownFeeBalanceRepository, loanTransactionRepository,
|
||||
loanMaximumAmountCalculator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+5
-1
@@ -195,6 +195,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
|
||||
private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository;
|
||||
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
||||
private final LoanMaximumAmountCalculator loanMaximumAmountCalculator;
|
||||
|
||||
@Override
|
||||
public LoanAccountData retrieveOne(final Long loanId) {
|
||||
@@ -492,7 +493,10 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
||||
capitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loanId).stream()
|
||||
.map(LoanCapitalizedIncomeBalance::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
}
|
||||
transactionAmount = loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
|
||||
transactionAmount = loan.getLoanProduct().isAllowApprovedDisbursedAmountsOverApplied()
|
||||
? loanMaximumAmountCalculator.getOverAppliedMax(loan)
|
||||
: loan.getApprovedPrincipal();
|
||||
transactionAmount = transactionAmount.subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
|
||||
paymentOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||
loanTransactionData = LoanTransactionData.loanTransactionDataForCreditTemplate(
|
||||
LoanEnumerations.transactionType(transactionType), DateUtils.getBusinessLocalDate(), transactionAmount,
|
||||
|
||||
+4
-2
@@ -143,6 +143,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementService
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanMaximumAmountCalculator;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanOfficerService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformServiceImpl;
|
||||
@@ -347,7 +348,7 @@ public class LoanAccountConfiguration {
|
||||
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService,
|
||||
LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
|
||||
LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository,
|
||||
@Lazy InterestRefundServiceDelegate interestRefundServiceDelegate) {
|
||||
@Lazy InterestRefundServiceDelegate interestRefundServiceDelegate, LoanMaximumAmountCalculator loanMaximumAmountCalculator) {
|
||||
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
|
||||
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
|
||||
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
|
||||
@@ -355,7 +356,8 @@ public class LoanAccountConfiguration {
|
||||
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
|
||||
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
|
||||
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService,
|
||||
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate);
|
||||
loanCapitalizedIncomeBalanceRepository, loanBuyDownFeeBalanceRepository, interestRefundServiceDelegate,
|
||||
loanMaximumAmountCalculator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+32
-1
@@ -101,7 +101,7 @@ public class LoanTransactionTest extends BaseLoanIntegrationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLoanTransactionTemplateForCapitalizedIncome() {
|
||||
public void testGetLoanTransactionTemplateForCapitalizedIncomeWithOverAppliedAmount() {
|
||||
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||
@@ -114,6 +114,37 @@ public class LoanTransactionTest extends BaseLoanIntegrationTest {
|
||||
|
||||
final String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
runAt("20 December 2024", () -> {
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
|
||||
430.0, 7.0, 6, (request) -> request.externalId(loanExternalIdStr));
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(230), "20 December 2024");
|
||||
|
||||
final GetLoansLoanIdTransactionsTemplateResponse transactionTemplate = loanTransactionHelper.retrieveTransactionTemplate(loanId,
|
||||
capitalizedIncomeCommand, null, null, null);
|
||||
|
||||
assertNotNull(transactionTemplate);
|
||||
assertEquals("loanTransactionType." + capitalizedIncomeCommand, transactionTemplate.getType().getCode());
|
||||
assertEquals(transactionTemplate.getAmount(), 415);
|
||||
assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetLoanTransactionTemplateForCapitalizedIncomeWithoutOverAppliedAmount() {
|
||||
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
|
||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
|
||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
|
||||
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
|
||||
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
|
||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE).overAppliedCalculationType(null)
|
||||
.overAppliedNumber(null).allowApprovedDisbursedAmountsOverApplied(false));
|
||||
|
||||
final String loanExternalIdStr = UUID.randomUUID().toString();
|
||||
|
||||
runAt("20 December 2024", () -> {
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "20 December 2024",
|
||||
430.0, 7.0, 6, (request) -> request.externalId(loanExternalIdStr));
|
||||
|
||||
Reference in New Issue
Block a user