FINERACT-2081: Loan account summary interest refund field
This commit is contained in:
committed by
Adam Saghy
parent
b1798a66df
commit
8d43565b09
+17
@@ -18,6 +18,9 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.api;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
|
||||
public interface LoanApiConstants {
|
||||
|
||||
String fixedEmiAmountParameterName = "fixedEmiAmount";
|
||||
@@ -173,4 +176,18 @@ public interface LoanApiConstants {
|
||||
|
||||
// Data Validator names
|
||||
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.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
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.loanschedule.data.LoanScheduleData;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Immutable data object representing loan summary information.
|
||||
@@ -96,6 +96,7 @@ public class LoanSummaryData {
|
||||
private BigDecimal totalRepaymentTransaction;
|
||||
private BigDecimal totalRepaymentTransactionReversed;
|
||||
private BigDecimal totalInterestPaymentWaiver;
|
||||
private BigDecimal totalInterestRefund;
|
||||
private final Long chargeOffReasonId;
|
||||
private final String chargeOffReason;
|
||||
|
||||
@@ -103,7 +104,7 @@ public class LoanSummaryData {
|
||||
private BigDecimal totalUnpaidAccruedNotDueInterest;
|
||||
|
||||
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 totalMerchantRefundReversed = BigDecimal.ZERO;
|
||||
@@ -119,32 +120,44 @@ public class LoanSummaryData {
|
||||
BigDecimal totalRepaymentTransaction = BigDecimal.ZERO;
|
||||
BigDecimal totalRepaymentTransactionReversed = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestPaymentWaiver = BigDecimal.ZERO;
|
||||
BigDecimal totalInterestRefund = BigDecimal.ZERO;
|
||||
BigDecimal totalUnpaidAccruedDueInterest = 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,
|
||||
loanTransactions);
|
||||
totalMerchantRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.MERCHANT_ISSUED_REFUND,
|
||||
loanTransactions);
|
||||
totalPayoutRefund = computeTotalAmountForNonReversedTransactions(LoanTransactionType.PAYOUT_REFUND, loanTransactions);
|
||||
totalPayoutRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.PAYOUT_REFUND, loanTransactions);
|
||||
totalGoodwillCredit = computeTotalAmountForNonReversedTransactions(LoanTransactionType.GOODWILL_CREDIT, loanTransactions);
|
||||
totalGoodwillCreditReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.GOODWILL_CREDIT, loanTransactions);
|
||||
totalChargeAdjustment = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT, loanTransactions);
|
||||
totalChargeAdjustmentReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.CHARGE_ADJUSTMENT,
|
||||
loanTransactions);
|
||||
totalChargeback = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CHARGEBACK, loanTransactions);
|
||||
totalCreditBalanceRefund = computeTotalAmountForNonReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
|
||||
loanTransactions);
|
||||
totalCreditBalanceRefundReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.CREDIT_BALANCE_REFUND,
|
||||
loanTransactions);
|
||||
totalRepaymentTransaction = computeTotalRepaymentTransactionAmount(loanTransactions);
|
||||
totalRepaymentTransactionReversed = computeTotalAmountForReversedTransactions(LoanTransactionType.REPAYMENT, loanTransactions);
|
||||
totalInterestPaymentWaiver = computeTotalAmountForNonReversedTransactions(LoanTransactionType.INTEREST_PAYMENT_WAIVER,
|
||||
loanTransactions);
|
||||
}
|
||||
totalChargeback = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.CHARGEBACK.getValue());
|
||||
|
||||
totalCreditBalanceRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
|
||||
totalCreditBalanceRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||
LoanTransactionType.CREDIT_BALANCE_REFUND.getValue());
|
||||
|
||||
totalGoodwillCredit = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.GOODWILL_CREDIT.getValue());
|
||||
totalGoodwillCreditReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||
LoanTransactionType.GOODWILL_CREDIT.getValue());
|
||||
|
||||
totalInterestRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances, LoanTransactionType.INTEREST_REFUND.getValue());
|
||||
|
||||
totalInterestPaymentWaiver = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||
LoanTransactionType.INTEREST_PAYMENT_WAIVER.getValue());
|
||||
|
||||
totalMerchantRefund = fetchLoanTransactionBalanceByType(loanTransactionBalances,
|
||||
LoanTransactionType.MERCHANT_ISSUED_REFUND.getValue());
|
||||
totalMerchantRefundReversed = fetchLoanTransactionBalanceReversedByType(loanTransactionBalances,
|
||||
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) {
|
||||
// Accrued Due Interest on Past due installments
|
||||
@@ -200,35 +213,29 @@ public class LoanSummaryData {
|
||||
.totalCreditBalanceRefundReversed(totalCreditBalanceRefundReversed).totalRepaymentTransaction(totalRepaymentTransaction)
|
||||
.totalRepaymentTransactionReversed(totalRepaymentTransactionReversed).totalInterestPaymentWaiver(totalInterestPaymentWaiver)
|
||||
.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) {
|
||||
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) {
|
||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user