FINERACT-2081: Loan account summary interest refund field
This commit is contained in:
committed by
Adam Saghy
parent
b1798a66df
commit
8d43565b09
@@ -450,6 +450,14 @@
|
|||||||
"null",
|
"null",
|
||||||
"bigdecimal"
|
"bigdecimal"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"default": null,
|
||||||
|
"name": "totalInterestRefund",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"bigdecimal"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+17
@@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.loanaccount.api;
|
package org.apache.fineract.portfolio.loanaccount.api;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||||
|
|
||||||
public interface LoanApiConstants {
|
public interface LoanApiConstants {
|
||||||
|
|
||||||
String fixedEmiAmountParameterName = "fixedEmiAmount";
|
String fixedEmiAmountParameterName = "fixedEmiAmount";
|
||||||
@@ -173,4 +176,18 @@ public interface LoanApiConstants {
|
|||||||
|
|
||||||
// Data Validator names
|
// Data Validator names
|
||||||
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
|
String LOAN_FRAUD_DATAVALIDATOR_PREFIX = "loans.fraud";
|
||||||
|
|
||||||
|
// Loan Summary Transaction Types
|
||||||
|
List<Integer> LOAN_SUMMARY_TRANSACTION_TYPES = List.of(LoanTransactionType.CHARGE_ADJUSTMENT.getValue(), //
|
||||||
|
LoanTransactionType.CHARGEBACK.getValue(), //
|
||||||
|
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), //
|
||||||
|
LoanTransactionType.DOWN_PAYMENT.getValue(), //
|
||||||
|
LoanTransactionType.GOODWILL_CREDIT.getValue(), //
|
||||||
|
LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue(), //
|
||||||
|
LoanTransactionType.INTEREST_REFUND.getValue(), //
|
||||||
|
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue(), //
|
||||||
|
LoanTransactionType.PAYOUT_REFUND.getValue(), //
|
||||||
|
LoanTransactionType.REPAYMENT.getValue() //
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-47
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.experimental.Accessors;
|
import lombok.experimental.Accessors;
|
||||||
@@ -30,7 +31,6 @@ import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable data object representing loan summary information.
|
* Immutable data object representing loan summary information.
|
||||||
@@ -96,6 +96,7 @@ public class LoanSummaryData {
|
|||||||
private BigDecimal totalRepaymentTransaction;
|
private BigDecimal totalRepaymentTransaction;
|
||||||
private BigDecimal totalRepaymentTransactionReversed;
|
private BigDecimal totalRepaymentTransactionReversed;
|
||||||
private BigDecimal totalInterestPaymentWaiver;
|
private BigDecimal totalInterestPaymentWaiver;
|
||||||
|
private BigDecimal totalInterestRefund;
|
||||||
private final Long chargeOffReasonId;
|
private final Long chargeOffReasonId;
|
||||||
private final String chargeOffReason;
|
private final String chargeOffReason;
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ public class LoanSummaryData {
|
|||||||
private BigDecimal totalUnpaidAccruedNotDueInterest;
|
private BigDecimal totalUnpaidAccruedNotDueInterest;
|
||||||
|
|
||||||
public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
|
public static LoanSummaryData withTransactionAmountsSummary(final LoanSummaryData defaultSummaryData,
|
||||||
final Collection<LoanTransactionData> loanTransactions, final LoanScheduleData repaymentSchedule) {
|
final LoanScheduleData repaymentSchedule, final Collection<LoanTransactionBalance> loanTransactionBalances) {
|
||||||
|
|
||||||
BigDecimal totalMerchantRefund = BigDecimal.ZERO;
|
BigDecimal totalMerchantRefund = BigDecimal.ZERO;
|
||||||
BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
|
BigDecimal totalMerchantRefundReversed = BigDecimal.ZERO;
|
||||||
@@ -119,32 +120,44 @@ public class LoanSummaryData {
|
|||||||
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
|
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
|
||||||
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
|
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
|
||||||
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
|
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
|
||||||
|
BigDecimal totalInterestRefund = BigDecimal.ZERO;
|
||||||
BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO;
|
BigDecimal totalUnpaidAccruedDueInterest = BigDecimal.ZERO;
|
||||||
BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO;
|
BigDecimal totalUnpaidAccruedNotDueInterest = BigDecimal.ZERO;
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(loanTransactions)) {
|
totalChargeAdjustment = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||||
|
LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
|
||||||
|
totalChargeAdjustmentReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
|
LoanTransactionType.CHARGE_ADJUSTMENT.getValue());
|
||||||
|
|
||||||
totalMerchantRefund = computeTotalAmountForNonReversedTransactions(LoanTransactionType.MERCHANT_ISSUED_REFUND,
|
totalChargeback = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.CHARGEBACK.getValue());
|
||||||
loanTransactions);
|
|
||||||
totalMerchantRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.MERCHANT_ISSUED_REFUND,
|
totalCreditBalanceRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||||
loanTransactions);
|
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
|
||||||
totalPayoutRefund = computeTotalAmountForNonReversedTransactions(LoanTransactionType.PAYOUT_REFUND, loanTransactions);
|
totalCreditBalanceRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
totalPayoutRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.PAYOUT_REFUND, loanTransactions);
|
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
|
||||||
totalGoodwillCredit = computeTotalAmountForNonReversedTransactions(LoanTransactionType.GOODWILL_CREDIT, loanTransactions);
|
|
||||||
totalGoodwillCreditReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.GOODWILL_CREDIT, loanTransactions);
|
totalGoodwillCredit = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.GOODWILL_CREDIT.getValue());
|
||||||
totalChargeAdjustment = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT, loanTransactions);
|
totalGoodwillCreditReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
totalChargeAdjustmentReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT,
|
LoanTransactionType.GOODWILL_CREDIT.getValue());
|
||||||
loanTransactions);
|
|
||||||
totalChargeback = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGEBACK, loanTransactions);
|
totalInterestRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.INTEREST_REFUND.getValue());
|
||||||
totalCreditBalanceRefund = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
|
|
||||||
loanTransactions);
|
totalInterestPaymentWaiver = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||||
totalCreditBalanceRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
|
LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue());
|
||||||
loanTransactions);
|
|
||||||
totalRepaymentTransaction = computeTotalRepaymentTransactionAmount(loanTransactions);
|
totalMerchantRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||||
totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions);
|
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
|
||||||
totalInterestPaymentWaiver = computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER,
|
totalMerchantRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
loanTransactions);
|
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
|
||||||
}
|
|
||||||
|
totalPayoutRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.PAYOUT_REFUND.getValue());
|
||||||
|
totalPayoutRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
|
LoanTransactionType.PAYOUT_REFUND.getValue());
|
||||||
|
|
||||||
|
totalRepaymentTransaction = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.REPAYMENT.getValue())
|
||||||
|
.add(fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.DOWN_PAYMENT.getValue()));
|
||||||
|
totalRepaymentTransactionReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||||
|
LoanTransactionType.REPAYMENT.getValue());
|
||||||
|
|
||||||
if (repaymentSchedule != null) {
|
if (repaymentSchedule != null) {
|
||||||
// Accrued Due Interest on Past due installments
|
// Accrued Due Interest on Past due installments
|
||||||
@@ -200,35 +213,29 @@ public class LoanSummaryData {
|
|||||||
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
|
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
|
||||||
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
|
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
|
||||||
.totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest)
|
.totalUnpaidAccruedDueInterest(totalUnpaidAccruedDueInterest)
|
||||||
.totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).build();
|
.totalUnpaidAccruedNotDueInterest(totalUnpaidAccruedNotDueInterest).totalInterestRefund(totalInterestRefund).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal fetchLoanTransactionBalanceByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
|
||||||
|
final Integer transactionType) {
|
||||||
|
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||||
|
.filter(balance -> balance.getTransactionType().equals(transactionType) && !balance.isReversed()).findFirst();
|
||||||
|
return optLoanTransactionBalance.isPresent() ? optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BigDecimal fetchLoanTransactionBalanceReversedByType(final Collection<LoanTransactionBalance> loanTransactionBalances,
|
||||||
|
final Integer transactionType) {
|
||||||
|
final Optional<LoanTransactionBalance> optLoanTransactionBalance = loanTransactionBalances.stream()
|
||||||
|
.filter(balance -> balance.getTransactionType().equals(transactionType) && balance.isReversed()
|
||||||
|
&& balance.isManuallyAdjustedOrReversed())
|
||||||
|
.findFirst();
|
||||||
|
return optLoanTransactionBalance.isPresent() ? optLoanTransactionBalance.get().getAmount() : BigDecimal.ZERO;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
|
public static LoanSummaryData withOnlyCurrencyData(CurrencyData currencyData) {
|
||||||
return LoanSummaryData.builder().currency(currencyData).build();
|
return LoanSummaryData.builder().currency(currencyData).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static BigDecimal computeTotalAmountForReversedTransactions(LoanTransactionType transactionType,
|
|
||||||
Collection<LoanTransactionData> loanTransactions) {
|
|
||||||
return loanTransactions.stream().filter(
|
|
||||||
transaction -> transaction.getType().getCode().equals(transactionType.getCode()) && transaction.getReversedOnDate() != null)
|
|
||||||
.map(txn -> txn.getAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal computeTotalAmountForNonReversedTransactions(LoanTransactionType transactionType,
|
|
||||||
Collection<LoanTransactionData> loanTransactions) {
|
|
||||||
return loanTransactions.stream().filter(
|
|
||||||
transaction -> transaction.getType().getCode().equals(transactionType.getCode()) && transaction.getReversedOnDate() == null)
|
|
||||||
.map(txn -> txn.getAmount()).reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal computeTotalRepaymentTransactionAmount(Collection<LoanTransactionData> loanTransactions) {
|
|
||||||
BigDecimal totalRepaymentTransaction = computeTotalAmountForNonReversedTransactions(LoanTransactionType.REPAYMENT,
|
|
||||||
loanTransactions);
|
|
||||||
BigDecimal totalDownPaymentTransaction = computeTotalAmountForNonReversedTransactions(LoanTransactionType.DOWN_PAYMENT,
|
|
||||||
loanTransactions);
|
|
||||||
return totalRepaymentTransaction.add(totalDownPaymentTransaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static BigDecimal computeTotalAccruedDueInterestAmount(Collection<LoanSchedulePeriodData> periods) {
|
private static BigDecimal computeTotalAccruedDueInterestAmount(Collection<LoanSchedulePeriodData> periods) {
|
||||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||||
return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate()))
|
return periods.stream().filter(period -> !period.getDownPaymentPeriod() && businessDate.isAfter(period.getDueDate()))
|
||||||
|
|||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* 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.data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class LoanTransactionBalance {
|
||||||
|
|
||||||
|
private final Integer transactionType;
|
||||||
|
private final boolean reversed;
|
||||||
|
private final boolean manuallyAdjustedOrReversed;
|
||||||
|
private final BigDecimal amount;
|
||||||
|
|
||||||
|
}
|
||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* 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 jakarta.persistence.EntityManager;
|
||||||
|
import jakarta.persistence.TypedQuery;
|
||||||
|
import jakarta.persistence.criteria.CriteriaBuilder;
|
||||||
|
import jakarta.persistence.criteria.CriteriaQuery;
|
||||||
|
import jakarta.persistence.criteria.Path;
|
||||||
|
import jakarta.persistence.criteria.Predicate;
|
||||||
|
import jakarta.persistence.criteria.Root;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.fineract.infrastructure.core.jpa.CriteriaQueryFactory;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionBalance;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LoanSummaryBalancesRepository {
|
||||||
|
|
||||||
|
private final EntityManager entityManager;
|
||||||
|
private final CriteriaQueryFactory criteriaQueryFactory;
|
||||||
|
|
||||||
|
public Collection<LoanTransactionBalance> retrieveLoanSummaryBalancesByTransactionType(final Long loanId,
|
||||||
|
final List<Integer> transactionTypes) {
|
||||||
|
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||||
|
CriteriaQuery<LoanTransactionBalance> query = cb.createQuery(LoanTransactionBalance.class);
|
||||||
|
|
||||||
|
Root<LoanTransaction> root = query.from(LoanTransaction.class);
|
||||||
|
|
||||||
|
Specification<LoanTransaction> spec = (r, q, builder) -> {
|
||||||
|
Path<Loan> la = r.get("loan");
|
||||||
|
|
||||||
|
List<Predicate> predicates = new ArrayList<>();
|
||||||
|
predicates.add(cb.equal(la.get("id"), loanId)); // Loan
|
||||||
|
predicates.add(r.get("typeOf").in(transactionTypes)); // Transaction Type
|
||||||
|
|
||||||
|
return cb.and(predicates.toArray(new Predicate[0]));
|
||||||
|
};
|
||||||
|
|
||||||
|
criteriaQueryFactory.applySpecificationToCriteria(root, spec, query);
|
||||||
|
|
||||||
|
query.groupBy(root.get("typeOf"), root.get("reversed"), root.get("manuallyAdjustedOrReversed"));
|
||||||
|
query.select(cb.construct(LoanTransactionBalance.class, root.get("typeOf"), root.get("reversed"),
|
||||||
|
root.get("manuallyAdjustedOrReversed"), cb.sum(root.get("amount"))));
|
||||||
|
|
||||||
|
TypedQuery<LoanTransactionBalance> queryToExecute = entityManager.createQuery(query);
|
||||||
|
return queryToExecute.getResultList();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+6
-4
@@ -31,11 +31,12 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusines
|
|||||||
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper;
|
import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanAccountDataMapper;
|
||||||
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
|
import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
|
||||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -49,6 +50,7 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer {
|
|||||||
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
|
private final LoanChargeReadPlatformService loanChargeReadPlatformService;
|
||||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||||
private final LoanInstallmentLevelDelinquencyEventProducer installmentLevelDelinquencyEventProducer;
|
private final LoanInstallmentLevelDelinquencyEventProducer installmentLevelDelinquencyEventProducer;
|
||||||
|
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T> boolean canSerialize(BusinessEvent<T> event) {
|
public <T> boolean canSerialize(BusinessEvent<T> event) {
|
||||||
@@ -72,9 +74,9 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer {
|
|||||||
data.setDelinquent(delinquentData);
|
data.setDelinquent(delinquentData);
|
||||||
|
|
||||||
if (data.getSummary() != null) {
|
if (data.getSummary() != null) {
|
||||||
final Collection<LoanTransactionData> currentLoanTransactions = service.retrieveLoanTransactions(loanId);
|
data.setSummary(LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), data.getRepaymentSchedule(),
|
||||||
data.setSummary(
|
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanId,
|
||||||
LoanSummaryData.withTransactionAmountsSummary(data.getSummary(), currentLoanTransactions, data.getRepaymentSchedule()));
|
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
|
||||||
} else {
|
} else {
|
||||||
data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency()));
|
data.setSummary(LoanSummaryData.withOnlyCurrencyData(data.getCurrency()));
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -130,6 +130,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
|||||||
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryBalancesRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanTemplateTypeRequiredException;
|
||||||
@@ -281,6 +282,7 @@ public class LoansApiResource {
|
|||||||
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory;
|
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory;
|
||||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||||
private final SqlValidator sqlValidator;
|
private final SqlValidator sqlValidator;
|
||||||
|
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
|
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
|
||||||
@@ -1114,8 +1116,9 @@ public class LoansApiResource {
|
|||||||
|
|
||||||
// updating summary with transaction amounts summary
|
// updating summary with transaction amounts summary
|
||||||
if (loanBasicDetails.getSummary() != null) {
|
if (loanBasicDetails.getSummary() != null) {
|
||||||
loanBasicDetails.setSummary(
|
loanBasicDetails.setSummary(LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), repaymentSchedule,
|
||||||
LoanSummaryData.withTransactionAmountsSummary(loanBasicDetails.getSummary(), currentLoanRepayments, repaymentSchedule));
|
loanSummaryBalancesRepository.retrieveLoanSummaryBalancesByTransactionType(loanBasicDetails.getId(),
|
||||||
|
LoanApiConstants.LOAN_SUMMARY_TRANSACTION_TYPES)));
|
||||||
}
|
}
|
||||||
|
|
||||||
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
|
final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments,
|
||||||
|
|||||||
+2
@@ -643,6 +643,8 @@ final class LoansApiResourceSwagger {
|
|||||||
public BigDecimal totalUnpaidAccruedDueInterest;
|
public BigDecimal totalUnpaidAccruedDueInterest;
|
||||||
@Schema(example = "0.000000")
|
@Schema(example = "0.000000")
|
||||||
public BigDecimal totalUnpaidAccruedNotDueInterest;
|
public BigDecimal totalUnpaidAccruedNotDueInterest;
|
||||||
|
@Schema(example = "0.000000")
|
||||||
|
public BigDecimal totalInterestRefund;
|
||||||
public Set<GetLoansLoanIdOverdueCharges> overdueCharges;
|
public Set<GetLoansLoanIdOverdueCharges> overdueCharges;
|
||||||
@Schema(example = "1")
|
@Schema(example = "1")
|
||||||
public Long chargeOffReasonId;
|
public Long chargeOffReasonId;
|
||||||
|
|||||||
+61
@@ -4836,6 +4836,67 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UC146: Validate totalInterestRefund using Advanced payment allocation with Interest Refund options
|
||||||
|
// 1. Create a Loan product with Adv. Pment. Alloc. and with Accrual and Interest Refund
|
||||||
|
// 2. Submit, Approve and Disburse a Loan account
|
||||||
|
// 3. Apply the MERCHANT_ISSUED_REFUND tranasaction to validate totalInterestRefund different than Zero
|
||||||
|
@Test
|
||||||
|
public void uc146() {
|
||||||
|
AtomicLong createdLoanId = new AtomicLong();
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||||
|
final ArrayList<String> interestRefundTypes = new ArrayList<String>();
|
||||||
|
interestRefundTypes.add("PAYOUT_REFUND");
|
||||||
|
interestRefundTypes.add("MERCHANT_ISSUED_REFUND");
|
||||||
|
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||||
|
.interestRatePerPeriod(12.0).interestCalculationPeriodType(RepaymentFrequencyType.DAYS).interestRateFrequencyType(YEARS)
|
||||||
|
.daysInMonthType(DaysInMonthType.ACTUAL.getValue()).daysInYearType(DaysInYearType.DAYS_365.getValue())
|
||||||
|
.numberOfRepayments(4)//
|
||||||
|
.repaymentEvery(5)//
|
||||||
|
.repaymentFrequencyType(0L)//
|
||||||
|
.allowPartialPeriodInterestCalcualtion(false)//
|
||||||
|
.multiDisburseLoan(false)//
|
||||||
|
.disallowExpectedDisbursements(null)//
|
||||||
|
.allowApprovedDisbursedAmountsOverApplied(null)//
|
||||||
|
.overAppliedCalculationType(null)//
|
||||||
|
.overAppliedNumber(null)//
|
||||||
|
.installmentAmountInMultiplesOf(null)//
|
||||||
|
.supportedInterestRefundTypes(interestRefundTypes) //
|
||||||
|
;//
|
||||||
|
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||||
|
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "1 January 2024", 1000.0,
|
||||||
|
4);
|
||||||
|
|
||||||
|
applicationRequest = applicationRequest.interestCalculationPeriodType(DAYS)
|
||||||
|
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||||
|
|
||||||
|
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||||
|
createdLoanId.set(loanResponse.getLoanId());
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000.0)).dateFormat(DATETIME_PATTERN)
|
||||||
|
.approvedOnDate("1 January 2024").locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||||
|
new PostLoansLoanIdRequest().actualDisbursementDate("1 January 2024").dateFormat(DATETIME_PATTERN)
|
||||||
|
.transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
// After Disbursement we are expecting zero interest refund
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
|
||||||
|
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||||
|
});
|
||||||
|
|
||||||
|
runAt("10 January 2024", () -> {
|
||||||
|
loanTransactionHelper.makeMerchantIssuedRefund(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest()
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionDate("10 January 2024").locale("en").transactionAmount(300.0));
|
||||||
|
|
||||||
|
// After Interest refund transaction we are expecting non zero interest refund
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||||
|
LOG.info("value {}", loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||||
|
assertEquals(BigDecimal.ZERO, loanDetails.getSummary().getTotalInterestRefund().stripTrailingZeros());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||||
|
|||||||
+4
-5
@@ -31,6 +31,7 @@ import java.time.format.DateTimeFormatter;
|
|||||||
import java.time.format.DateTimeFormatterBuilder;
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
|
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
|
||||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
@@ -52,12 +53,11 @@ import org.junit.jupiter.api.BeforeEach;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
@ExtendWith(LoanTestLifecycleExtension.class)
|
@ExtendWith(LoanTestLifecycleExtension.class)
|
||||||
public class LoanTransactionSummaryTest {
|
public class LoanTransactionSummaryTest {
|
||||||
|
|
||||||
private ResponseSpecification responseSpec;
|
private ResponseSpecification responseSpec;
|
||||||
private ResponseSpecification responseSpecErr400;
|
|
||||||
private ResponseSpecification responseSpecErr503;
|
|
||||||
private RequestSpecification requestSpec;
|
private RequestSpecification requestSpec;
|
||||||
private ClientHelper clientHelper;
|
private ClientHelper clientHelper;
|
||||||
private LoanTransactionHelper loanTransactionHelper;
|
private LoanTransactionHelper loanTransactionHelper;
|
||||||
@@ -69,8 +69,6 @@ public class LoanTransactionSummaryTest {
|
|||||||
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||||
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||||
this.responseSpecErr400 = new ResponseSpecBuilder().expectStatusCode(400).build();
|
|
||||||
this.responseSpecErr503 = new ResponseSpecBuilder().expectStatusCode(503).build();
|
|
||||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||||
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
|
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
|
||||||
}
|
}
|
||||||
@@ -86,13 +84,14 @@ public class LoanTransactionSummaryTest {
|
|||||||
delinquencyBucketId);
|
delinquencyBucketId);
|
||||||
|
|
||||||
// Client and Loan account creation
|
// Client and Loan account creation
|
||||||
|
|
||||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||||
|
log.info("Client Id {}", clientId);
|
||||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
|
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = createLoanProduct(loanTransactionHelper,
|
||||||
delinquencyBucketId);
|
delinquencyBucketId);
|
||||||
assertNotNull(getLoanProductsProductResponse);
|
assertNotNull(getLoanProductsProductResponse);
|
||||||
|
|
||||||
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr);
|
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr);
|
||||||
|
log.info("Loan Id {}", loanId);
|
||||||
|
|
||||||
// make Repayments
|
// make Repayments
|
||||||
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
|
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
|
||||||
|
|||||||
Reference in New Issue
Block a user