diff --git a/custom/acme/loan/processor/src/main/java/com/acme/fineract/loan/processor/AcmeLoanRepaymentScheduleTransactionProcessor.java b/custom/acme/loan/processor/src/main/java/com/acme/fineract/loan/processor/AcmeLoanRepaymentScheduleTransactionProcessor.java
index c8ae5147f..6054e2e0d 100644
--- a/custom/acme/loan/processor/src/main/java/com/acme/fineract/loan/processor/AcmeLoanRepaymentScheduleTransactionProcessor.java
+++ b/custom/acme/loan/processor/src/main/java/com/acme/fineract/loan/processor/AcmeLoanRepaymentScheduleTransactionProcessor.java
@@ -20,6 +20,8 @@ package com.acme.fineract.loan.processor;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.springframework.stereotype.Component;
@Component
@@ -29,8 +31,9 @@ public class AcmeLoanRepaymentScheduleTransactionProcessor extends FineractStyle
public static final String STRATEGY_NAME = "ACME Corp.: standard loan transaction processing strategy";
- public AcmeLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public AcmeLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-investor/src/test/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializerTest.java b/fineract-investor/src/test/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializerTest.java
index 8783e9251..07fad8281 100644
--- a/fineract-investor/src/test/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializerTest.java
+++ b/fineract-investor/src/test/java/org/apache/fineract/investor/service/serialization/serializer/investor/InvestorBusinessEventSerializerTest.java
@@ -40,9 +40,10 @@ import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.time.LocalDate;
-import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.fineract.avro.generator.ByteBufferSerializable;
@@ -135,7 +136,7 @@ public class InvestorBusinessEventSerializerTest {
.thenReturn(createTransferData(firstTransferStatus, null));
Loan loan = Mockito.mock(Loan.class);
when(loan.getCurrency()).thenReturn(new MonetaryCurrency("EUR", 2, 1));
- List loanCharges = createMockCharges();
+ final Set loanCharges = createMockCharges();
when(loan.getLoanCharges()).thenReturn(loanCharges);
LoanOwnershipTransferBusinessEvent loanOwnershipTransferBusinessEvent = new LoanOwnershipTransferBusinessEvent(
createExternalAssetOwnerTransfer(status, subStatus), loan);
@@ -153,8 +154,8 @@ public class InvestorBusinessEventSerializerTest {
assertEquals(CUSTOM_DATA_PREFIX + "_2", new String(customData.get("test_key_2").array(), UTF_8));
}
- private List createMockCharges() {
- List loanCharges = new ArrayList<>();
+ private Set createMockCharges() {
+ final Set loanCharges = new HashSet<>();
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("10.00000")));
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("15.00000")));
loanCharges.add(loanCharge(2L, "charge b", BigDecimal.ZERO));
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
index e99319a62..694e0bfef 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyReadPlatformServiceImpl.java
@@ -18,6 +18,9 @@
*/
package org.apache.fineract.portfolio.delinquency.service;
+import static org.apache.fineract.portfolio.loanaccount.domain.Loan.EARLIEST_UNPAID_DATE;
+import static org.apache.fineract.portfolio.loanaccount.domain.Loan.NEXT_UNPAID_DUE_DATE;
+
import java.time.LocalDate;
import java.util.Collection;
import java.util.Comparator;
@@ -27,6 +30,7 @@ import java.util.stream.Collector;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
@@ -51,6 +55,7 @@ import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
import org.apache.fineract.portfolio.loanaccount.data.DelinquencyPausePeriod;
import org.apache.fineract.portfolio.loanaccount.data.InstallmentLevelDelinquency;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.jetbrains.annotations.NotNull;
@@ -142,7 +147,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
// loans
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()));
- collectionData.setNextPaymentDueDate(loan.possibleNextRepaymentDate(nextPaymentDueDateConfig));
+ collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
if (lastPayment != null) {
@@ -228,4 +233,63 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
return List.of();
}
+ private LocalDate possibleNextRepaymentDate(final String nextPaymentDueDateConfig, final Loan loan) {
+ if (nextPaymentDueDateConfig == null) {
+ return null;
+ }
+ return switch (nextPaymentDueDateConfig.toLowerCase()) {
+ case EARLIEST_UNPAID_DATE -> getEarliestUnpaidInstallmentDate(loan);
+ case NEXT_UNPAID_DUE_DATE -> getNextUnpaidInstallmentDueDate(loan);
+ default -> null;
+ };
+ }
+
+ private LocalDate getEarliestUnpaidInstallmentDate(final Loan loan) {
+ LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment installment : installments) {
+ if (installment.isNotFullyPaidOff()) {
+ earliestUnpaidInstallmentDate = installment.getDueDate();
+ break;
+ }
+ }
+
+ LocalDate lastTransactionDate = null;
+ for (final LoanTransaction transaction : loan.getLoanTransactions()) {
+ if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
+ lastTransactionDate = transaction.getTransactionDate();
+ }
+ }
+
+ LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate;
+ if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) {
+ possibleNextRepaymentDate = lastTransactionDate;
+ }
+
+ return possibleNextRepaymentDate;
+ }
+
+ private LocalDate getNextUnpaidInstallmentDueDate(final Loan loan) {
+ List installments = loan.getRepaymentScheduleInstallments();
+ LocalDate currentBusinessDate = DateUtils.getBusinessLocalDate();
+ LocalDate expectedMaturityDate = loan.determineExpectedMaturityDate();
+ LocalDate nextUnpaidInstallmentDate = expectedMaturityDate;
+
+ for (final LoanRepaymentScheduleInstallment installment : installments) {
+ boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
+ && DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
+ if (installment.isDownPayment()) {
+ isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isEqual(currentBusinessDate, installment.getDueDate())
+ && DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
+ }
+ if (isCurrentDateBeforeInstallmentAndLoanPeriod) {
+ if (installment.isNotFullyPaidOff()) {
+ nextUnpaidInstallmentDate = installment.getDueDate();
+ break;
+ }
+ }
+ }
+ return nextUnpaidInstallmentDate;
+ }
+
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
index bccbe3f43..5872c4bbf 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachine.java
@@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.springframework.stereotype.Component;
// TODO: introduce tests for the state machine
@@ -36,6 +37,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
private static final List ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values());
private final BusinessEventNotifierService businessEventNotifierService;
+ private final LoanBalanceService loanBalanceService;
@Override
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
@@ -45,7 +47,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
@Override
public void transition(final LoanEvent loanEvent, final Loan loan) {
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
internalTransition(loanEvent, loan);
}
@@ -55,7 +57,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
return;
}
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
final LoanStatusTransition transition = determineTransition(loan, loan.getStatus(), transactionDate);
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 4f22c7489..a11d7c710 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -67,7 +67,6 @@ import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.charge.domain.Charge;
-import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -76,7 +75,6 @@ import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
-import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -275,6 +273,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "interest_calculated_from_date")
private LocalDate interestChargedFromDate;
+ @Setter
@Column(name = "total_overpaid_derived", scale = 6, precision = 19)
private BigDecimal totalOverpaid;
@@ -347,6 +346,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List loanTermVariations = new ArrayList<>();
+ @Setter
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
private BigDecimal totalRecovered;
@@ -716,28 +716,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
}
- public void updateLoanSummaryDerivedFields() {
- if (isNotDisbursed()) {
- if (this.summary != null) {
- this.summary.zeroFields();
- }
- this.totalOverpaid = null;
- } else {
- final Money overpaidBy = calculateTotalOverpayment();
- this.totalOverpaid = null;
- if (!overpaidBy.isLessThanZero()) {
- this.totalOverpaid = overpaidBy.getAmountDefaultedToNullIfZero();
- }
-
- final Money recoveredAmount = calculateTotalRecoveredPayments();
- this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
-
- final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
- this.summary.updateSummary(getCurrency(), principal, getRepaymentScheduleInstallments(), this.charges);
- updateLoanOutstandingBalances();
- }
- }
-
private boolean isInterestRecalculationEnabledForProduct() {
return this.loanProduct.isInterestRecalculationEnabled();
}
@@ -746,33 +724,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.loanProduct.isMultiDisburseLoan();
}
- public BigDecimal calculateOverdueAmountPercentageAppliedTo(final LoanCharge loanCharge, final int penaltyWaitPeriod) {
- LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment();
- LocalDate graceDate = DateUtils.getBusinessLocalDate().minusDays(penaltyWaitPeriod);
- Money amount = Money.zero(getCurrency());
-
- if (DateUtils.isAfter(graceDate, installment.getDueDate())) {
- amount = calculateOverdueAmountPercentageAppliedTo(installment, loanCharge.getChargeCalculation());
- if (!amount.isGreaterThanZero()) {
- loanCharge.setActive(false);
- }
- } else {
- loanCharge.setActive(false);
- }
- return amount.getAmount();
- }
-
- private Money calculateOverdueAmountPercentageAppliedTo(LoanRepaymentScheduleInstallment installment,
- ChargeCalculationType calculationType) {
- return switch (calculationType) {
- case PERCENT_OF_AMOUNT -> installment.getPrincipalOutstanding(getCurrency());
- case PERCENT_OF_AMOUNT_AND_INTEREST ->
- installment.getPrincipalOutstanding(getCurrency()).plus(installment.getInterestOutstanding(getCurrency()));
- case PERCENT_OF_INTEREST -> installment.getInterestOutstanding(getCurrency());
- default -> Money.zero(getCurrency());
- };
- }
-
public List fetchLoanTrancheChargeIds() {
return getCharges().stream()//
.filter(charge -> charge.isTrancheDisbursementCharge() && charge.isActive()) //
@@ -793,7 +744,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
.collect(Collectors.toList());
}
- private LocalDate determineExpectedMaturityDate() {
+ public LocalDate determineExpectedMaturityDate() {
List installments = getRepaymentScheduleInstallments().stream()
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
final int numberOfInstallments = installments.size();
@@ -918,16 +869,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.postDatedChecks = new ArrayList<>();
}
- public List retrieveListOfTransactionsForReprocessing() {
- return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE)
- .collect(Collectors.toList());
- }
-
- private static Predicate loanTransactionForReprocessingPredicate() {
- return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
- || transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
- }
-
public List retrieveListOfTransactionsExcludeAccruals() {
final List repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
@@ -945,95 +886,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
}
- public boolean isChronologicallyLatestRepaymentOrWaiver(final LoanTransaction loanTransaction) {
- boolean isChronologicallyLatestRepaymentOrWaiver = true;
-
- final LocalDate currentTransactionDate = loanTransaction.getTransactionDate();
- for (final LoanTransaction previousTransaction : loanTransactions) {
- if (!previousTransaction.isDisbursement() && previousTransaction.isNotReversed()
- && (DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())
- || (DateUtils.isEqual(currentTransactionDate, previousTransaction.getTransactionDate())
- && ((loanTransaction.getId() == null && previousTransaction.getId() == null)
- || (loanTransaction.getId() != null && (previousTransaction.getId() == null
- || loanTransaction.getId().compareTo(previousTransaction.getId()) < 0)))))) {
- isChronologicallyLatestRepaymentOrWaiver = false;
- break;
- }
- }
- return isChronologicallyLatestRepaymentOrWaiver;
- }
-
public boolean isAfterLastRepayment(final LoanTransaction loanTransaction, final List loanTransactions) {
return loanTransactions.stream() //
.filter(t -> t.isRepaymentLikeType() && t.isNotReversed()) //
.noneMatch(t -> DateUtils.isBefore(loanTransaction.getTransactionDate(), t.getTransactionDate()));
}
- public boolean isChronologicallyLatestTransaction(final LoanTransaction loanTransaction, final List loanTransactions) {
- return loanTransactions.stream() //
- .filter(LoanTransaction::isNotReversed) //
- .allMatch(t -> DateUtils.isAfter(loanTransaction.getTransactionDate(), t.getTransactionDate()));
- }
-
- public LocalDate possibleNextRepaymentDate(final String nextPaymentDueDateConfig) {
- if (nextPaymentDueDateConfig == null) {
- return null;
- }
- return switch (nextPaymentDueDateConfig.toLowerCase()) {
- case EARLIEST_UNPAID_DATE -> getEarliestUnpaidInstallmentDate();
- case NEXT_UNPAID_DUE_DATE -> getNextUnpaidInstallmentDueDate();
- default -> null;
- };
- }
-
- private LocalDate getNextUnpaidInstallmentDueDate() {
- List installments = getRepaymentScheduleInstallments();
- LocalDate currentBusinessDate = DateUtils.getBusinessLocalDate();
- LocalDate expectedMaturityDate = determineExpectedMaturityDate();
- LocalDate nextUnpaidInstallmentDate = expectedMaturityDate;
-
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
- && DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
- if (installment.isDownPayment()) {
- isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isEqual(currentBusinessDate, installment.getDueDate())
- && DateUtils.isBefore(currentBusinessDate, expectedMaturityDate);
- }
- if (isCurrentDateBeforeInstallmentAndLoanPeriod) {
- if (installment.isNotFullyPaidOff()) {
- nextUnpaidInstallmentDate = installment.getDueDate();
- break;
- }
- }
- }
- return nextUnpaidInstallmentDate;
- }
-
- private LocalDate getEarliestUnpaidInstallmentDate() {
- LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
- List installments = getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- if (installment.isNotFullyPaidOff()) {
- earliestUnpaidInstallmentDate = installment.getDueDate();
- break;
- }
- }
-
- LocalDate lastTransactionDate = null;
- for (final LoanTransaction transaction : this.loanTransactions) {
- if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
- lastTransactionDate = transaction.getTransactionDate();
- }
- }
-
- LocalDate possibleNextRepaymentDate = earliestUnpaidInstallmentDate;
- if (DateUtils.isAfter(lastTransactionDate, earliestUnpaidInstallmentDate)) {
- possibleNextRepaymentDate = lastTransactionDate;
- }
-
- return possibleNextRepaymentDate;
- }
-
public LoanTransaction findWriteOffTransaction() {
return this.loanTransactions.stream() //
.filter(transaction -> !transaction.isReversed() && transaction.isWriteOff()) //
@@ -1041,49 +899,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
.orElse(null);
}
- public boolean isOverPaid() {
- return calculateTotalOverpayment().isGreaterThanZero();
- }
-
- public Money calculateTotalOverpayment() {
- Money totalPaidInRepayments = getTotalPaidInRepayments();
-
- final MonetaryCurrency currency = getCurrency();
- Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
- Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
- List installments = getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
- cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments
- .plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
- .plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
-
- cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
- }
-
- for (final LoanTransaction loanTransaction : this.loanTransactions) {
- if (loanTransaction.isReversed()) {
- continue;
- }
- if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
- totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
- } else if (loanTransaction.isCreditBalanceRefund()) {
- if (loanTransaction.getPrincipalPortion(currency).isZero()) {
- totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
- }
- } else if (loanTransaction.isChargeback()) {
- if (loanTransaction.getPrincipalPortion(currency).isZero() && getCreditAllocationRules().stream() //
- .filter(car -> car.getTransactionType().equals(CreditAllocationTransactionType.CHARGEBACK)) //
- .findAny() //
- .isEmpty()) {
- totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
- }
- }
- }
-
- // if total paid in transactions doesn't match repayment schedule then there's an overpayment.
- return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
- }
-
public Money calculateTotalRecoveredPayments() {
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
return getTotalRecoveredPayments();
@@ -1392,30 +1207,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return isChargedOff() && getChargeOffReason() != null ? getChargeOffReason().getId() : null;
}
- public Money getReceivableInterest(final LocalDate tillDate) {
- Money receivableInterest = Money.zero(getCurrency());
- for (final LoanTransaction transaction : this.loanTransactions) {
- if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement()
- && !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
- if (transaction.isAccrual()) {
- receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
- } else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver() || transaction.isAccrualAdjustment()) {
- receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
- }
- }
- if (receivableInterest.isLessThanZero()) {
- receivableInterest = receivableInterest.zero();
- }
- /*
- * if (transaction.getTransactionDate().isAfter(tillDate) && transaction.isAccrual()) { final String
- * errorMessage = "The date on which a loan is interest waived cannot be in after accrual transactions." ;
- * throw new InvalidLoanStateTransitionException("waive", "cannot.be.after.accrual.date", errorMessage,
- * tillDate); }
- */
- }
- return receivableInterest;
- }
-
public boolean isSyncDisbursementWithMeeting() {
return this.syncDisbursementWithMeeting != null && this.syncDisbursementWithMeeting;
}
@@ -1480,13 +1271,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return currentTransactionDate;
}
- public LoanTransaction getLastTransactionForReprocessing() {
- return loanTransactions.stream() //
- .filter(Loan.loanTransactionForReprocessingPredicate()) //
- .reduce((first, second) -> second) //
- .orElse(null);
- }
-
public LoanTransaction getLastPaymentTransaction() {
return loanTransactions.stream() //
.filter(loanTransaction -> !loanTransaction.isReversed()) //
@@ -1507,6 +1291,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.charges == null ? new HashSet<>() : this.charges.stream().filter(LoanCharge::isActive).collect(Collectors.toSet());
}
+ public LoanCharge fetchLoanChargesById(final Long id) {
+ LoanCharge charge = null;
+ for (LoanCharge loanCharge : this.charges) {
+ if (id.equals(loanCharge.getId())) {
+ charge = loanCharge;
+ break;
+ }
+ }
+ return charge;
+ }
+
public List getAllDisbursementDetails() {
return this.disbursementDetails;
}
@@ -1800,21 +1595,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
}
- public void updateLoanToLastDisbursalState(LoanDisbursementDetails disbursementDetail) {
- for (final LoanCharge charge : getActiveCharges()) {
- if (charge.isOverdueInstallmentCharge()) {
- charge.setActive(false);
- } else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
- .equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
- charge.resetToOriginal(getCurrency());
- }
- }
- this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(disbursementDetail.principal()));
- disbursementDetail.updateActualDisbursementDate(null);
- disbursementDetail.reverse();
- updateLoanSummaryDerivedFields();
- }
-
private int adjustNumberOfRepayments() {
int repaymetsForAdjust = 0;
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
@@ -1867,145 +1647,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.writeOffReason = writeOffReason;
}
- public LoanRepaymentScheduleInstallment fetchLoanForeclosureDetail(final LocalDate closureDate) {
- Money[] receivables = retrieveIncomeOutstandingTillDate(closureDate);
- Money totalPrincipal = Money.of(getCurrency(), this.getSummary().getTotalPrincipalOutstanding());
- totalPrincipal = totalPrincipal.minus(receivables[3]);
- final LocalDate currentDate = DateUtils.getBusinessLocalDate();
- return new LoanRepaymentScheduleInstallment(null, 0, currentDate, currentDate, totalPrincipal.getAmount(),
- receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, null);
- }
-
- private Money[] retrieveIncomeOutstandingTillDate(final LocalDate paymentDate) {
- Money[] balances = new Money[4];
- final MonetaryCurrency currency = getCurrency();
- Money interest = Money.zero(currency);
- Money paidFromFutureInstallments = Money.zero(currency);
- Money fee = Money.zero(currency);
- Money penalty = Money.zero(currency);
- int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
- .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
-
- for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
- boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
- if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) {
- interest = interest.plus(installment.getInterestOutstanding(currency));
- penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
- fee = fee.plus(installment.getFeeChargesOutstanding(currency));
- } else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) {
- Money[] balancesForCurrentPeroid = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment,
- isFirstNormalInstallment);
- if (balancesForCurrentPeroid[0].isGreaterThan(balancesForCurrentPeroid[5])) {
- interest = interest.plus(balancesForCurrentPeroid[0]).minus(balancesForCurrentPeroid[5]);
- } else {
- paidFromFutureInstallments = paidFromFutureInstallments.plus(balancesForCurrentPeroid[5])
- .minus(balancesForCurrentPeroid[0]);
- }
- if (balancesForCurrentPeroid[1].isGreaterThan(balancesForCurrentPeroid[3])) {
- fee = fee.plus(balancesForCurrentPeroid[1].minus(balancesForCurrentPeroid[3]));
- } else {
- paidFromFutureInstallments = paidFromFutureInstallments
- .plus(balancesForCurrentPeroid[3].minus(balancesForCurrentPeroid[1]));
- }
- if (balancesForCurrentPeroid[2].isGreaterThan(balancesForCurrentPeroid[4])) {
- penalty = penalty.plus(balancesForCurrentPeroid[2].minus(balancesForCurrentPeroid[4]));
- } else {
- paidFromFutureInstallments = paidFromFutureInstallments.plus(balancesForCurrentPeroid[4])
- .minus(balancesForCurrentPeroid[2]);
- }
- } else {
- paidFromFutureInstallments = paidFromFutureInstallments.plus(installment.getInterestPaid(currency))
- .plus(installment.getPenaltyChargesPaid(currency)).plus(installment.getFeeChargesPaid(currency));
- }
-
- }
- balances[0] = interest;
- balances[1] = fee;
- balances[2] = penalty;
- balances[3] = paidFromFutureInstallments;
- return balances;
- }
-
- private Money[] fetchInterestFeeAndPenaltyTillDate(final LocalDate paymentDate, final MonetaryCurrency currency,
- final LoanRepaymentScheduleInstallment installment, boolean isFirstNormalInstallment) {
- Money penaltyForCurrentPeriod = Money.zero(getCurrency());
- Money penaltyAccoutedForCurrentPeriod = Money.zero(getCurrency());
- Money feeForCurrentPeriod = Money.zero(getCurrency());
- Money feeAccountedForCurrentPeriod = Money.zero(getCurrency());
- int totalPeriodDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate());
- int tillDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), paymentDate);
- Money interestForCurrentPeriod = Money.of(getCurrency(), BigDecimal
- .valueOf(calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(getCurrency()).getAmount(), tillDays)));
- Money interestAccountedForCurrentPeriod = installment.getInterestWaived(getCurrency())
- .plus(installment.getInterestPaid(getCurrency()));
- for (LoanCharge loanCharge : this.charges) {
- if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
- boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment);
- if (isDue) {
- if (loanCharge.isPenaltyCharge()) {
- penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(getCurrency()));
- penaltyAccoutedForCurrentPeriod = penaltyAccoutedForCurrentPeriod
- .plus(loanCharge.getAmountWaived(getCurrency()).plus(loanCharge.getAmountPaid(getCurrency())));
- } else {
- feeForCurrentPeriod = feeForCurrentPeriod.plus(loanCharge.getAmount(currency));
- feeAccountedForCurrentPeriod = feeAccountedForCurrentPeriod.plus(loanCharge.getAmountWaived(getCurrency()).plus(
-
- loanCharge.getAmountPaid(getCurrency())));
- }
- } else if (loanCharge.isInstalmentFee()) {
- LoanInstallmentCharge loanInstallmentCharge = loanCharge.getInstallmentLoanCharge(installment.getInstallmentNumber());
- if (loanCharge.isPenaltyCharge()) {
- penaltyAccoutedForCurrentPeriod = penaltyAccoutedForCurrentPeriod
- .plus(loanInstallmentCharge.getAmountPaid(currency));
- } else {
- feeAccountedForCurrentPeriod = feeAccountedForCurrentPeriod.plus(loanInstallmentCharge.getAmountPaid(currency));
- }
- }
- }
- }
-
- Money[] balances = new Money[6];
- balances[0] = interestForCurrentPeriod;
- balances[1] = feeForCurrentPeriod;
- balances[2] = penaltyForCurrentPeriod;
- balances[3] = feeAccountedForCurrentPeriod;
- balances[4] = penaltyAccoutedForCurrentPeriod;
- balances[5] = interestAccountedForCurrentPeriod;
- return balances;
- }
-
- public Money[] retrieveIncomeForOverlappingPeriod(final LocalDate paymentDate) {
- Money[] balances = new Money[3];
- final MonetaryCurrency currency = getCurrency();
- balances[0] = balances[1] = balances[2] = Money.zero(currency);
- int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
- .fetchFirstNormalInstallmentNumber(repaymentScheduleInstallments);
- for (final LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
- boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
- if (DateUtils.isEqual(paymentDate, installment.getDueDate())) {
- Money interest = installment.getInterestCharged(currency);
- Money fee = installment.getFeeChargesCharged(currency);
- Money penalty = installment.getPenaltyChargesCharged(currency);
- balances[0] = interest;
- balances[1] = fee;
- balances[2] = penalty;
- break;
- } else if (DateUtils.isDateInRangeExclusive(paymentDate, installment.getFromDate(), installment.getDueDate())) {
- balances = fetchInterestFeeAndPenaltyTillDate(paymentDate, currency, installment, isFirstNormalInstallment);
- break;
- }
- }
-
- return balances;
- }
-
- private double calculateInterestForDays(int daysInPeriod, BigDecimal interest, int days) {
- if (interest.doubleValue() == 0) {
- return 0;
- }
- return interest.doubleValue() / daysInPeriod * days;
- }
-
public void updateLoanScheduleOnForeclosure(final Collection installments) {
this.repaymentScheduleInstallments.clear();
for (final LoanRepaymentScheduleInstallment installment : installments) {
@@ -2046,7 +1687,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.loanTopupDetails;
}
- public Collection getLoanCharges() {
+ public Set getLoanCharges() {
return this.charges;
}
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 bacf7f02f..b5c21a78a 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
@@ -33,18 +33,13 @@ import java.math.BigDecimal;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
import java.util.HashSet;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import lombok.Getter;
import lombok.Setter;
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
@@ -56,11 +51,11 @@ import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
-import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
+@Setter
@Getter
@Entity
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
@@ -104,7 +99,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "amount_paid_derived", scale = 6, precision = 19)
private BigDecimal amountPaid;
- @Setter
@Column(name = "amount_waived_derived", scale = 6, precision = 19)
private BigDecimal amountWaived;
@@ -149,111 +143,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
@OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set loanChargePaidBySet = new HashSet<>();
- protected LoanCharge() {
- //
- }
-
- public LoanCharge(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
- final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
- final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanCharge,
- final ExternalId externalId) {
- this.loan = loan;
- this.charge = chargeDefinition;
- this.submittedOnDate = DateUtils.getBusinessLocalDate();
- this.penaltyCharge = chargeDefinition.isPenalty();
- this.minCap = chargeDefinition.getMinCap();
- this.maxCap = chargeDefinition.getMaxCap();
-
- this.chargeTime = chargeDefinition.getChargeTimeType();
- if (chargeTime != null) {
- this.chargeTime = chargeTime.getValue();
- }
-
- if (ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.SPECIFIED_DUE_DATE)
- || ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.OVERDUE_INSTALLMENT)) {
-
- if (dueDate == null) {
- final String defaultUserMessage = "Loan charge is missing due date.";
- throw new LoanChargeWithoutMandatoryFieldException("loanCharge", "dueDate", defaultUserMessage, chargeDefinition.getId(),
- chargeDefinition.getName());
- }
-
- this.dueDate = dueDate;
- } else {
- this.dueDate = null;
- }
-
- this.chargeCalculation = chargeDefinition.getChargeCalculation();
- if (chargeCalculation != null) {
- this.chargeCalculation = chargeCalculation.getValue();
- }
-
- BigDecimal chargeAmount = chargeDefinition.getAmount();
- if (amount != null) {
- chargeAmount = amount;
- }
-
- this.chargePaymentMode = chargeDefinition.getChargePaymentMode();
- if (chargePaymentMode != null) {
- this.chargePaymentMode = chargePaymentMode.getValue();
- }
-
- populateDerivedFields(loanPrincipal, chargeAmount, numberOfRepayments, loanCharge);
- this.paid = determineIfFullyPaid();
- this.externalId = externalId;
- }
-
- public void populateDerivedFields(final BigDecimal amountPercentageAppliedTo, final BigDecimal chargeAmount, Integer numberOfRepayments,
- BigDecimal loanCharge) {
-
- switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
- case INVALID:
- this.percentage = null;
- this.amount = null;
- this.amountPercentageAppliedTo = null;
- this.amountPaid = null;
- this.amountOutstanding = BigDecimal.ZERO;
- this.amountWaived = null;
- this.amountWrittenOff = null;
- break;
- case FLAT:
- this.percentage = null;
- this.amountPercentageAppliedTo = null;
- this.amountPaid = null;
- if (isInstalmentFee()) {
- if (numberOfRepayments == null) {
- numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
- }
- this.amount = chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments));
- } else {
- this.amount = chargeAmount;
- }
- this.amountOutstanding = this.amount;
- this.amountWaived = null;
- this.amountWrittenOff = null;
- break;
- case PERCENT_OF_AMOUNT:
- case PERCENT_OF_AMOUNT_AND_INTEREST:
- case PERCENT_OF_INTEREST:
- case PERCENT_OF_DISBURSEMENT_AMOUNT:
- this.percentage = chargeAmount;
- this.amountPercentageAppliedTo = amountPercentageAppliedTo;
- if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
- loanCharge = percentageOf(this.amountPercentageAppliedTo);
- }
- this.amount = minimumAndMaximumCap(loanCharge);
- this.amountPaid = null;
- this.amountOutstanding = calculateOutstanding();
- this.amountWaived = null;
- this.amountWrittenOff = null;
- break;
- }
- this.amountOrPercentage = chargeAmount;
- if (this.loan != null && isInstalmentFee()) {
- updateInstallmentCharges();
- }
- }
-
public void markAsFullyPaid() {
this.amountPaid = this.amount;
this.amountOutstanding = BigDecimal.ZERO;
@@ -340,234 +229,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
this.loan = loan;
}
- public void update(final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal, Integer numberOfRepayments,
- BigDecimal loanCharge) {
- if (dueDate != null) {
- this.dueDate = dueDate;
- }
-
- if (amount != null) {
- switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
- case INVALID:
- break;
- case FLAT:
- if (isInstalmentFee()) {
- if (numberOfRepayments == null) {
- numberOfRepayments = this.loan.fetchNumberOfInstallmensAfterExceptions();
- }
- this.amount = amount.multiply(BigDecimal.valueOf(numberOfRepayments));
- } else {
- this.amount = amount;
- }
- break;
- case PERCENT_OF_AMOUNT:
- case PERCENT_OF_AMOUNT_AND_INTEREST:
- case PERCENT_OF_INTEREST:
- case PERCENT_OF_DISBURSEMENT_AMOUNT:
- this.percentage = amount;
- this.amountPercentageAppliedTo = loanPrincipal;
- if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
- loanCharge = percentageOf(this.amountPercentageAppliedTo);
- }
- this.amount = minimumAndMaximumCap(loanCharge);
- break;
- }
- this.amountOrPercentage = amount;
- this.amountOutstanding = calculateOutstanding();
- if (this.loan != null && isInstalmentFee()) {
- updateInstallmentCharges();
- }
- }
- }
-
- public void update(final BigDecimal amount, final LocalDate dueDate, final Integer numberOfRepayments) {
- BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
- if (this.loan != null) {
- switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
- case PERCENT_OF_AMOUNT:
- // If charge type is specified due date and loan is multi disburment loan.
- // Then we need to get as of this loan charge due date how much amount disbursed.
- if (this.loan.isMultiDisburmentLoan() && this.isSpecifiedDueDate()) {
- for (final LoanDisbursementDetails loanDisbursementDetails : this.loan.getDisbursementDetails()) {
- if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), this.getDueDate())) {
- amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
- }
- }
- } else {
- amountPercentageAppliedTo = this.loan.getPrincipal().getAmount();
- }
- break;
- case PERCENT_OF_AMOUNT_AND_INTEREST:
- amountPercentageAppliedTo = this.loan.getPrincipal().getAmount().add(this.loan.getTotalInterest());
- break;
- case PERCENT_OF_INTEREST:
- amountPercentageAppliedTo = this.loan.getTotalInterest();
- break;
- case PERCENT_OF_DISBURSEMENT_AMOUNT:
- LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = this.loanTrancheDisbursementCharge;
- amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal();
- break;
- default:
- break;
- }
- }
- update(amount, dueDate, amountPercentageAppliedTo, numberOfRepayments, BigDecimal.ZERO);
- }
-
- public Map update(final JsonCommand command, final BigDecimal amount) {
-
- final Map actualChanges = new LinkedHashMap<>(7);
-
- final String dateFormatAsInput = command.dateFormat();
- final String localeAsInput = command.locale();
-
- final String dueDateParamName = "dueDate";
- if (command.isChangeInLocalDateParameterNamed(dueDateParamName, getDueLocalDate())) {
- final String valueAsInput = command.stringValueOfParameterNamed(dueDateParamName);
- actualChanges.put(dueDateParamName, valueAsInput);
- actualChanges.put("dateFormat", dateFormatAsInput);
- actualChanges.put("locale", localeAsInput);
-
- this.dueDate = command.localDateValueOfParameterNamed(dueDateParamName);
- }
-
- final String amountParamName = "amount";
- if (command.isChangeInBigDecimalParameterNamed(amountParamName, this.amount)) {
- final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
- BigDecimal loanCharge = null;
- actualChanges.put(amountParamName, newValue);
- actualChanges.put("locale", localeAsInput);
- switch (ChargeCalculationType.fromInt(this.chargeCalculation)) {
- case INVALID:
- break;
- case FLAT:
- if (isInstalmentFee()) {
- this.amount = newValue.multiply(BigDecimal.valueOf(this.loan.fetchNumberOfInstallmensAfterExceptions()));
- } else {
- this.amount = newValue;
- }
- this.amountOutstanding = calculateOutstanding();
- break;
- case PERCENT_OF_AMOUNT:
- case PERCENT_OF_AMOUNT_AND_INTEREST:
- case PERCENT_OF_INTEREST:
- case PERCENT_OF_DISBURSEMENT_AMOUNT:
- this.percentage = newValue;
- this.amountPercentageAppliedTo = amount;
- loanCharge = BigDecimal.ZERO;
- if (isInstalmentFee()) {
- loanCharge = calculatePerInstallmentChargeAmount(loan, ChargeCalculationType.fromInt(this.chargeCalculation),
- this.percentage);
- }
- if (loanCharge.compareTo(BigDecimal.ZERO) == 0) {
- loanCharge = percentageOf(this.amountPercentageAppliedTo);
- }
- this.amount = minimumAndMaximumCap(loanCharge);
- this.amountOutstanding = calculateOutstanding();
- break;
- }
- this.amountOrPercentage = newValue;
- if (isInstalmentFee()) {
- updateInstallmentCharges();
- }
- }
- return actualChanges;
- }
-
- public static BigDecimal calculatePerInstallmentChargeAmount(final Loan loan, final ChargeCalculationType calculationType,
- final BigDecimal percentage) {
- Money amount = Money.zero(loan.getCurrency());
- List installments = loan.getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- amount = amount.plus(calculateInstallmentChargeAmount(loan, calculationType, percentage, installment));
- }
- return amount.getAmount();
- }
-
- public BigDecimal calculatePerInstallmentChargeAmount() {
- Loan loan = this.loan;
- Money amount = Money.zero(loan.getCurrency());
- List installments = loan.getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- amount = amount.plus(calculateInstallmentChargeAmount(loan, getChargeCalculation(), getPercentage(), installment));
- }
- return amount.getAmount();
- }
-
- private List generateInstallmentLoanCharges(final LoanCharge loanCharge) {
- Loan loan = this.loan;
- final List loanChargePerInstallments = new ArrayList<>();
- if (loanCharge.isInstalmentFee()) {
- List installments = loan.getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- if (installment.isRecalculatedInterestComponent()) {
- continue;
- }
- BigDecimal amount;
- if (loanCharge.getChargeCalculation().isFlat()) {
- amount = loanCharge.amountOrPercentage();
- } else {
- amount = calculateInstallmentChargeAmount(loan, loanCharge.getChargeCalculation(), loanCharge.getPercentage(),
- installment).getAmount();
- }
- final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
- installment.getInstallmentCharges().add(loanInstallmentCharge);
- loanChargePerInstallments.add(loanInstallmentCharge);
- }
- }
- return loanChargePerInstallments;
- }
-
- private static Money calculateInstallmentChargeAmount(final Loan loan, final ChargeCalculationType calculationType,
- final BigDecimal percentage, final LoanRepaymentScheduleInstallment installment) {
- Money percentOf = switch (calculationType) {
- case PERCENT_OF_AMOUNT -> installment.getPrincipal(loan.getCurrency());
- case PERCENT_OF_AMOUNT_AND_INTEREST ->
- installment.getPrincipal(loan.getCurrency()).plus(installment.getInterestCharged(loan.getCurrency()));
- case PERCENT_OF_INTEREST -> installment.getInterestCharged(loan.getCurrency());
- case PERCENT_OF_DISBURSEMENT_AMOUNT, INVALID, FLAT -> Money.zero(loan.getCurrency());
-
- };
- return Money.zero(loan.getCurrency()) //
- .plus(LoanCharge.percentageOf(percentOf.getAmount(), percentage));
- }
-
- private void updateInstallmentCharges() {
- final Collection remove = new HashSet<>();
- final List newChargeInstallments = generateInstallmentLoanCharges(this);
- if (this.loanInstallmentCharge.isEmpty()) {
- this.loanInstallmentCharge.addAll(newChargeInstallments);
- } else {
- int index = 0;
- final List oldChargeInstallments = new ArrayList<>();
- if (this.loanInstallmentCharge != null && !this.loanInstallmentCharge.isEmpty()) {
- oldChargeInstallments.addAll(this.loanInstallmentCharge);
- }
- Collections.sort(oldChargeInstallments);
- final LoanInstallmentCharge[] loanChargePerInstallmentArray = newChargeInstallments
- .toArray(new LoanInstallmentCharge[newChargeInstallments.size()]);
- for (final LoanInstallmentCharge chargePerInstallment : oldChargeInstallments) {
- if (index == loanChargePerInstallmentArray.length) {
- remove.add(chargePerInstallment);
- chargePerInstallment.getInstallment().getInstallmentCharges().remove(chargePerInstallment);
- } else {
- LoanInstallmentCharge newLoanInstallmentCharge = loanChargePerInstallmentArray[index++];
- newLoanInstallmentCharge.getInstallment().getInstallmentCharges().remove(newLoanInstallmentCharge);
- chargePerInstallment.copyFrom(newLoanInstallmentCharge);
- }
- }
- this.loanInstallmentCharge.removeAll(remove);
- while (index < loanChargePerInstallmentArray.length) {
- this.loanInstallmentCharge.add(loanChargePerInstallmentArray[index++]);
- }
- }
- Money amount = Money.zero(this.loan.getCurrency());
- for (LoanInstallmentCharge charge : this.loanInstallmentCharge) {
- amount = amount.plus(charge.getAmount());
- }
- this.amount = amount.getAmount();
- }
-
public boolean isDueAtDisbursement() {
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT)
|| ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_DISBURSEMENT);
@@ -600,7 +261,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
}
- private BigDecimal calculateOutstanding() {
+ public BigDecimal calculateOutstanding() {
if (this.amount == null) {
return null;
}
@@ -645,7 +306,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
* @returns a minimum cap or maximum cap set on charges if the criteria fits else it returns the percentageOf if the
* amount is within min and max cap
*/
- private BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
+ public BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
BigDecimal minMaxCap;
if (this.minCap != null) {
final int minimumCap = percentageOf.compareTo(this.minCap);
@@ -817,11 +478,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom {
return ChargeCalculationType.fromInt(this.chargeCalculation);
}
- public void updateAmount(final BigDecimal amount) {
- this.amount = amount;
- calculateOutstanding();
- }
-
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
LoanInstallmentCharge unpaidChargePerInstallment = null;
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 9ca1949e6..55a29f5e7 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -925,4 +925,5 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom findLoanIdById(@Param("id") Long id);
+
+ @Query("""
+ SELECT CASE WHEN COUNT(lt) > 0 THEN false ELSE true END
+ FROM LoanTransaction lt
+ WHERE lt.loan = :loan
+ AND lt.reversed = false
+ AND lt.dateOf > :transactionDate
+ """)
+ boolean isChronologicallyLatest(@Param("transactionDate") LocalDate transactionDate, @Param("loan") Loan loan);
+
+ @Query("""
+ SELECT MAX(lt.dateOf) FROM LoanTransaction lt
+ WHERE lt.loan = :loan
+ AND lt.reversed = false
+ AND lt.typeOf NOT IN (
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CONTRA,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.MARKED_FOR_RESCHEDULING,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.ACCRUAL_ADJUSTMENT,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.APPROVE_TRANSFER,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.INITIATE_TRANSFER,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REJECT_TRANSFER,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.WITHDRAW_TRANSFER,
+ org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION
+ )
+ """)
+ Optional findLastTransactionDateForReprocessing(@Param("loan") Loan loan);
+
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
index e271c1378..a6aaa89ca 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java
@@ -61,6 +61,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.springframework.util.CollectionUtils;
/**
@@ -76,8 +77,9 @@ import org.springframework.util.CollectionUtils;
public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
protected final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
- protected final LoanChargeValidator loanChargeValidator = new LoanChargeValidator();
protected final ExternalIdFactory externalIdFactory;
+ protected final LoanChargeValidator loanChargeValidator;
+ protected final LoanBalanceService loanBalanceService;
@Override
public boolean accept(String s) {
@@ -256,7 +258,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} else {
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
final Loan loan = loanTransaction.getLoan();
- if ((loan.isClosedObligationsMet() || loan.isOverPaid()) && currentInstallment.isObligationsMet()
+ if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
index eed45dd84..2518955dd 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/CreocoreLoanRepaymentScheduleTransactionProcessor.java
@@ -30,6 +30,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* Creocore style {@link LoanRepaymentScheduleTransactionProcessor}.
@@ -47,8 +49,9 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
public static final String STRATEGY_NAME = "Creocore Unique";
- public CreocoreLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public CreocoreLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java
index fb82ae93b..e24e00f1d 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor.java
@@ -32,6 +32,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* `First due/late charges, interest, principal, after in advance principal, charges, interest` style
@@ -48,8 +50,9 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
public static final String STRATEGY_NAME = "Due penalty, fee, interest, principal, In advance principal, penalty, fee, interest";
- public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java
index acdee6768..148c0f4a2 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor.java
@@ -32,6 +32,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* `First due/late penalty, interest, principal, fee, after in advance penalty, interest, principal, fee` style
@@ -48,8 +50,9 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
public static final String STRATEGY_NAME = "Due penalty, interest, principal, fee, In advance penalty, interest, principal, fee";
- public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
index 8d7794a57..8d2d11e07 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/EarlyPaymentLoanRepaymentScheduleTransactionProcessor.java
@@ -30,6 +30,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the payment order of Interest first, then
@@ -41,8 +43,9 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr
public static final String STRATEGY_NAME = "Early Repayment Strategy";
- public EarlyPaymentLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public EarlyPaymentLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
index 0b3de1210..371784261 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/FineractStyleLoanRepaymentScheduleTransactionProcessor.java
@@ -30,6 +30,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* Old style {@link LoanRepaymentScheduleTransactionProcessor}.
@@ -47,8 +49,9 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
public static final String STRATEGY_NAME = "Penalties, Fees, Interest, Principal order";
- public FineractStyleLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public FineractStyleLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
index 67356bc58..fbafe9620 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/HeavensFamilyLoanRepaymentScheduleTransactionProcessor.java
@@ -31,6 +31,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* Heavensfamily style {@link LoanRepaymentScheduleTransactionProcessor}.
@@ -49,8 +51,9 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
public static final String STRATEGY_NAME = "HeavensFamily Unique";
- public HeavensFamilyLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public HeavensFamilyLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
index 5ed1824f6..d82dc0b29 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -30,6 +30,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the payment order of Interest first, then
@@ -42,8 +44,9 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
public static final String STRATEGY_NAME = "Interest, Principal, Penalties, Fees Order";
- public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
index a134067fb..5e46fa0a7 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor.java
@@ -30,6 +30,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* This {@link LoanRepaymentScheduleTransactionProcessor} defaults to having the payment order of principal first, then
@@ -42,8 +44,9 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
public static final String STRATEGY_NAME = "Principal, Interest, Penalties, Fees Order";
- public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
index 47ee06451..bbeadc84a 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/RBILoanRepaymentScheduleTransactionProcessor.java
@@ -31,6 +31,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
/**
* Adhikar/RBI style {@link LoanRepaymentScheduleTransactionProcessor}.
@@ -51,8 +53,9 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
public static final String STRATEGY_NAME = "Overdue/Due Fee/Int,Principal";
- public RBILoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- super(externalIdFactory);
+ public RBILoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Override
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
index 4bec98359..f92112531 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/AbstractCumulativeLoanScheduleGenerator.java
@@ -35,6 +35,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
+import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -61,10 +62,14 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
+@RequiredArgsConstructor
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
+ private final LoanTransactionService loanTransactionService;
+
@Override
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set loanCharges, final HolidayDetailDTO holidayDetailDTO) {
@@ -2796,7 +2801,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
- List loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
+ final List loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java
index 31c1bdfd3..a3ce6e25b 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeDecliningBalanceInterestLoanScheduleGenerator.java
@@ -25,10 +25,10 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
-import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
import org.springframework.stereotype.Component;
@@ -56,12 +56,19 @@ import org.springframework.stereotype.Component;
*
*/
@Component
-@RequiredArgsConstructor
public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator;
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
+ public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
+ final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
+ final LoanTransactionService loanTransactionService) {
+ super(loanTransactionService);
+ this.scheduledDateGenerator = scheduledDateGenerator;
+ this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
+ }
+
@Override
public ScheduledDateGenerator getScheduledDateGenerator() {
return scheduledDateGenerator;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java
index 852bd9b12..ad58d1f2f 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/CumulativeFlatInterestLoanScheduleGenerator.java
@@ -24,18 +24,25 @@ import java.time.LocalDate;
import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;
-import lombok.RequiredArgsConstructor;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.springframework.stereotype.Component;
@Component
-@RequiredArgsConstructor
public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator;
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
+ public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
+ final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
+ final LoanTransactionService loanTransactionService) {
+ super(loanTransactionService);
+ this.scheduledDateGenerator = scheduledDateGenerator;
+ this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
+ }
+
@Override
public ScheduledDateGenerator getScheduledDateGenerator() {
return scheduledDateGenerator;
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDownPaymentTransactionValidator.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDownPaymentTransactionValidator.java
index 507741ee4..b900baa6a 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDownPaymentTransactionValidator.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanDownPaymentTransactionValidator.java
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.serialization;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
+import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.holiday.domain.Holiday;
@@ -31,11 +32,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.springframework.stereotype.Component;
@Component
+@RequiredArgsConstructor
public final class LoanDownPaymentTransactionValidator {
+ private final LoanBalanceService loanBalanceService;
+
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay) {
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
@@ -53,7 +58,7 @@ public final class LoanDownPaymentTransactionValidator {
}
public void validateLoanStatusIsActiveOrFullyPaidOrOverpaid(final Loan loan) {
- if (!(loan.isOpen() || loan.isClosedObligationsMet() || loan.isOverPaid())) {
+ if (!(loan.isOpen() || loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan))) {
final List dataValidationErrors = new ArrayList<>();
final String defaultUserMessage = "Loan must be Active, Fully Paid or Overpaid";
final ApiParameterError error = ApiParameterError.generalError("error.msg.loan.must.be.active.fully.paid.or.overpaid",
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java
new file mode 100644
index 000000000..cb69a73eb
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBalanceService.java
@@ -0,0 +1,278 @@
+/**
+ * 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 java.time.LocalDate;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
+import org.springframework.stereotype.Service;
+
+@Service
+public class LoanBalanceService {
+
+ public Money calculateTotalOverpayment(final Loan loan) {
+ Money totalPaidInRepayments = loan.getTotalPaidInRepayments();
+
+ final MonetaryCurrency currency = loan.getCurrency();
+ Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
+ Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
+ cumulativeTotalPaidOnInstallments = cumulativeTotalPaidOnInstallments
+ .plus(scheduledRepayment.getPrincipalCompleted(currency).plus(scheduledRepayment.getInterestPaid(currency)))
+ .plus(scheduledRepayment.getFeeChargesPaid(currency)).plus(scheduledRepayment.getPenaltyChargesPaid(currency));
+
+ cumulativeTotalWaivedOnInstallments = cumulativeTotalWaivedOnInstallments.plus(scheduledRepayment.getInterestWaived(currency));
+ }
+
+ for (final LoanTransaction loanTransaction : loan.getLoanTransactions()) {
+ if (loanTransaction.isReversed()) {
+ continue;
+ }
+ if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
+ totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
+ } else if (loanTransaction.isCreditBalanceRefund()) {
+ if (loanTransaction.getPrincipalPortion(currency).isZero()) {
+ totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
+ }
+ } else if (loanTransaction.isChargeback()) {
+ if (loanTransaction.getPrincipalPortion(currency).isZero() && loan.getCreditAllocationRules().stream() //
+ .filter(car -> car.getTransactionType().equals(CreditAllocationTransactionType.CHARGEBACK)) //
+ .findAny() //
+ .isEmpty()) {
+ totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
+ }
+ }
+ }
+
+ // if total paid in transactions doesn't match repayment schedule then there's an overpayment.
+ return totalPaidInRepayments.minus(cumulativeTotalPaidOnInstallments);
+ }
+
+ public boolean isOverPaid(final Loan loan) {
+ return calculateTotalOverpayment(loan).isGreaterThanZero();
+ }
+
+ public void updateLoanSummaryDerivedFields(final Loan loan) {
+ if (loan.isNotDisbursed()) {
+ if (loan.getSummary() != null) {
+ loan.getSummary().zeroFields();
+ }
+ loan.setTotalOverpaid(null);
+ } else {
+ final Money overpaidBy = calculateTotalOverpayment(loan);
+ loan.setTotalOverpaid(null);
+ if (!overpaidBy.isLessThanZero()) {
+ loan.setTotalOverpaid(overpaidBy.getAmountDefaultedToNullIfZero());
+ }
+
+ final Money recoveredAmount = loan.calculateTotalRecoveredPayments();
+ loan.setTotalRecovered(recoveredAmount.getAmountDefaultedToNullIfZero());
+
+ final Money principal = loan.getLoanRepaymentScheduleDetail().getPrincipal();
+ loan.getSummary().updateSummary(loan.getCurrency(), principal, loan.getRepaymentScheduleInstallments(), loan.getLoanCharges());
+ loan.updateLoanOutstandingBalances();
+ }
+ }
+
+ public void updateLoanToLastDisbursalState(final Loan loan, final LoanDisbursementDetails disbursementDetail) {
+ for (final LoanCharge charge : loan.getActiveCharges()) {
+ if (charge.isOverdueInstallmentCharge()) {
+ charge.setActive(false);
+ } else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
+ .equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
+ charge.resetToOriginal(loan.getCurrency());
+ }
+ }
+ loan.getLoanRepaymentScheduleDetail().setPrincipal(loan.getDisbursedAmount().subtract(disbursementDetail.principal()));
+ disbursementDetail.updateActualDisbursementDate(null);
+ disbursementDetail.reverse();
+ updateLoanSummaryDerivedFields(loan);
+ }
+
+ public Money getReceivableInterest(final Loan loan, final LocalDate tillDate) {
+ Money receivableInterest = Money.zero(loan.getCurrency());
+ for (final LoanTransaction transaction : loan.getLoanTransactions()) {
+ if (transaction.isNotReversed() && !transaction.isRepaymentAtDisbursement() && !transaction.isDisbursement()
+ && !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
+ if (transaction.isAccrual()) {
+ receivableInterest = receivableInterest.plus(transaction.getInterestPortion(loan.getCurrency()));
+ } else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver() || transaction.isAccrualAdjustment()) {
+ receivableInterest = receivableInterest.minus(transaction.getInterestPortion(loan.getCurrency()));
+ }
+ }
+ if (receivableInterest.isLessThanZero()) {
+ receivableInterest = receivableInterest.zero();
+ }
+ }
+ return receivableInterest;
+ }
+
+ public LoanRepaymentScheduleInstallment fetchLoanForeclosureDetail(final Loan loan, final LocalDate closureDate) {
+ Money[] receivables = retrieveIncomeOutstandingTillDate(loan, closureDate);
+ Money totalPrincipal = Money.of(loan.getCurrency(), loan.getSummary().getTotalPrincipalOutstanding());
+ totalPrincipal = totalPrincipal.minus(receivables[3]);
+ final LocalDate currentDate = DateUtils.getBusinessLocalDate();
+ return new LoanRepaymentScheduleInstallment(null, 0, currentDate, currentDate, totalPrincipal.getAmount(),
+ receivables[0].getAmount(), receivables[1].getAmount(), receivables[2].getAmount(), false, null);
+ }
+
+ public Money[] retrieveIncomeForOverlappingPeriod(final Loan loan, final LocalDate paymentDate) {
+ Money[] balances = new Money[3];
+ final MonetaryCurrency currency = loan.getCurrency();
+ balances[0] = balances[1] = balances[2] = Money.zero(currency);
+ int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+ .fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
+ for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
+ boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
+ if (DateUtils.isEqual(paymentDate, installment.getDueDate())) {
+ Money interest = installment.getInterestCharged(currency);
+ Money fee = installment.getFeeChargesCharged(currency);
+ Money penalty = installment.getPenaltyChargesCharged(currency);
+ balances[0] = interest;
+ balances[1] = fee;
+ balances[2] = penalty;
+ break;
+ } else if (DateUtils.isDateInRangeExclusive(paymentDate, installment.getFromDate(), installment.getDueDate())) {
+ balances = fetchInterestFeeAndPenaltyTillDate(loan, paymentDate, currency, installment, isFirstNormalInstallment);
+ break;
+ }
+ }
+
+ return balances;
+ }
+
+ private Money[] retrieveIncomeOutstandingTillDate(final Loan loan, final LocalDate paymentDate) {
+ Money[] balances = new Money[4];
+ final MonetaryCurrency currency = loan.getCurrency();
+ Money interest = Money.zero(currency);
+ Money paidFromFutureInstallments = Money.zero(currency);
+ Money fee = Money.zero(currency);
+ Money penalty = Money.zero(currency);
+ int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+ .fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
+
+ for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
+ boolean isFirstNormalInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
+ if (!DateUtils.isBefore(paymentDate, installment.getDueDate())) {
+ interest = interest.plus(installment.getInterestOutstanding(currency));
+ penalty = penalty.plus(installment.getPenaltyChargesOutstanding(currency));
+ fee = fee.plus(installment.getFeeChargesOutstanding(currency));
+ } else if (DateUtils.isAfter(paymentDate, installment.getFromDate())) {
+ Money[] balancesForCurrentPeriod = fetchInterestFeeAndPenaltyTillDate(loan, paymentDate, currency, installment,
+ isFirstNormalInstallment);
+ if (balancesForCurrentPeriod[0].isGreaterThan(balancesForCurrentPeriod[5])) {
+ interest = interest.plus(balancesForCurrentPeriod[0]).minus(balancesForCurrentPeriod[5]);
+ } else {
+ paidFromFutureInstallments = paidFromFutureInstallments.plus(balancesForCurrentPeriod[5])
+ .minus(balancesForCurrentPeriod[0]);
+ }
+ if (balancesForCurrentPeriod[1].isGreaterThan(balancesForCurrentPeriod[3])) {
+ fee = fee.plus(balancesForCurrentPeriod[1].minus(balancesForCurrentPeriod[3]));
+ } else {
+ paidFromFutureInstallments = paidFromFutureInstallments
+ .plus(balancesForCurrentPeriod[3].minus(balancesForCurrentPeriod[1]));
+ }
+ if (balancesForCurrentPeriod[2].isGreaterThan(balancesForCurrentPeriod[4])) {
+ penalty = penalty.plus(balancesForCurrentPeriod[2].minus(balancesForCurrentPeriod[4]));
+ } else {
+ paidFromFutureInstallments = paidFromFutureInstallments.plus(balancesForCurrentPeriod[4])
+ .minus(balancesForCurrentPeriod[2]);
+ }
+ } else {
+ paidFromFutureInstallments = paidFromFutureInstallments.plus(installment.getInterestPaid(currency))
+ .plus(installment.getPenaltyChargesPaid(currency)).plus(installment.getFeeChargesPaid(currency));
+ }
+
+ }
+ balances[0] = interest;
+ balances[1] = fee;
+ balances[2] = penalty;
+ balances[3] = paidFromFutureInstallments;
+ return balances;
+ }
+
+ private Money[] fetchInterestFeeAndPenaltyTillDate(final Loan loan, final LocalDate paymentDate, final MonetaryCurrency currency,
+ final LoanRepaymentScheduleInstallment installment, final boolean isFirstNormalInstallment) {
+ Money penaltyForCurrentPeriod = Money.zero(loan.getCurrency());
+ Money penaltyAccoutedForCurrentPeriod = Money.zero(loan.getCurrency());
+ Money feeForCurrentPeriod = Money.zero(loan.getCurrency());
+ Money feeAccountedForCurrentPeriod = Money.zero(loan.getCurrency());
+ int totalPeriodDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), installment.getDueDate());
+ int tillDays = DateUtils.getExactDifferenceInDays(installment.getFromDate(), paymentDate);
+ Money interestForCurrentPeriod = Money.of(loan.getCurrency(), BigDecimal.valueOf(
+ calculateInterestForDays(totalPeriodDays, installment.getInterestCharged(loan.getCurrency()).getAmount(), tillDays)));
+ Money interestAccountedForCurrentPeriod = installment.getInterestWaived(loan.getCurrency())
+ .plus(installment.getInterestPaid(loan.getCurrency()));
+ for (LoanCharge loanCharge : loan.getLoanCharges()) {
+ if (loanCharge.isActive() && !loanCharge.isDueAtDisbursement()) {
+ boolean isDue = loanCharge.isDueInPeriod(installment.getFromDate(), paymentDate, isFirstNormalInstallment);
+ if (isDue) {
+ if (loanCharge.isPenaltyCharge()) {
+ penaltyForCurrentPeriod = penaltyForCurrentPeriod.plus(loanCharge.getAmount(loan.getCurrency()));
+ penaltyAccoutedForCurrentPeriod = penaltyAccoutedForCurrentPeriod
+ .plus(loanCharge.getAmountWaived(loan.getCurrency()).plus(loanCharge.getAmountPaid(loan.getCurrency())));
+ } else {
+ feeForCurrentPeriod = feeForCurrentPeriod.plus(loanCharge.getAmount(currency));
+ feeAccountedForCurrentPeriod = feeAccountedForCurrentPeriod
+ .plus(loanCharge.getAmountWaived(loan.getCurrency()).plus(
+
+ loanCharge.getAmountPaid(loan.getCurrency())));
+ }
+ } else if (loanCharge.isInstalmentFee()) {
+ LoanInstallmentCharge loanInstallmentCharge = loanCharge.getInstallmentLoanCharge(installment.getInstallmentNumber());
+ if (loanCharge.isPenaltyCharge()) {
+ penaltyAccoutedForCurrentPeriod = penaltyAccoutedForCurrentPeriod
+ .plus(loanInstallmentCharge.getAmountPaid(currency));
+ } else {
+ feeAccountedForCurrentPeriod = feeAccountedForCurrentPeriod.plus(loanInstallmentCharge.getAmountPaid(currency));
+ }
+ }
+ }
+ }
+
+ Money[] balances = new Money[6];
+ balances[0] = interestForCurrentPeriod;
+ balances[1] = feeForCurrentPeriod;
+ balances[2] = penaltyForCurrentPeriod;
+ balances[3] = feeAccountedForCurrentPeriod;
+ balances[4] = penaltyAccoutedForCurrentPeriod;
+ balances[5] = interestAccountedForCurrentPeriod;
+ return balances;
+ }
+
+ private double calculateInterestForDays(final int daysInPeriod, final BigDecimal interest, final int days) {
+ if (interest.doubleValue() == 0) {
+ return 0;
+ }
+ return interest.doubleValue() / daysInPeriod * days;
+ }
+
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java
index e28faaf1b..4cd54591f 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeService.java
@@ -21,20 +21,30 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.charge.domain.Charge;
+import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
+import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -52,6 +62,7 @@ public class LoanChargeService {
private final LoanChargeValidator loanChargeValidator;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
+ private final LoanBalanceService loanBalanceService;
public void recalculateAllCharges(final Loan loan) {
Set charges = loan.getActiveCharges();
@@ -68,54 +79,25 @@ public class LoanChargeService {
BigDecimal totalChargeAmt = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageBased()) {
if (loanCharge.isOverdueInstallmentCharge()) {
- amount = loan.calculateOverdueAmountPercentageAppliedTo(loanCharge, penaltyWaitPeriod);
+ amount = calculateOverdueAmountPercentageAppliedTo(loan, loanCharge, penaltyWaitPeriod);
} else {
amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
}
chargeAmt = loanCharge.getPercentage();
if (loanCharge.isInstalmentFee()) {
- totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
+ totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
}
} else {
chargeAmt = loanCharge.amountOrPercentage();
}
if (loanCharge.isActive()) {
clearLoanInstallmentChargesBeforeRegeneration(loanCharge);
- loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
+ update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
totalChargeAmt);
loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate());
}
}
- private void clearLoanInstallmentChargesBeforeRegeneration(final LoanCharge loanCharge) {
- /*
- * JW https://issues.apache.org/jira/browse/FINERACT-1557 For loan installment charges only : Clear down
- * installment charges from the loanCharge and from each of the repayment installments and allow them to be
- * recalculated fully anew. This patch is to avoid the 'merging' of existing and regenerated installment charges
- * which results in the installment charges being deleted on loan approval if the schedule is regenerated. Not
- * pretty. updateInstallmentCharges in LoanCharge.java: the merging looks like it will work but doesn't so this
- * patch simply hits the part which 'adds all' rather than merge. Possibly an ORM issue. The issue could be to
- * do with the fact that, on approval, the "recalculateLoanCharge" happens twice (probably 2 schedule
- * regenerations) whereas it only happens once on Submit and Disburse (and no problems with them)
- *
- * if (this.loanInstallmentCharge.isEmpty()) { this.loanInstallmentCharge.addAll(newChargeInstallments);
- */
- Loan loan = loanCharge.getLoan();
- if (!loan.isSubmittedAndPendingApproval() && !loan.isApproved()) {
- return;
- } // doing for both just in case status is not
- // updated at this points
- if (loanCharge.isInstalmentFee()) {
- loanCharge.clearLoanInstallmentCharges();
- for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
- if (installment.isRecalculatedInterestComponent()) {
- continue; // JW: does this in generateInstallmentLoanCharges - but don't understand it
- }
- installment.getInstallmentCharges().clear();
- }
- }
- }
-
public void makeChargePayment(final Loan loan, final Long chargeId, final List existingTransactionIds,
final List existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction);
@@ -130,36 +112,6 @@ public class LoanChargeService {
handleChargePaidTransaction(loan, charge, paymentTransaction, installmentNumber);
}
- public void handleChargePaidTransaction(final Loan loan, final LoanCharge charge, final LoanTransaction chargesPayment,
- final Integer installmentNumber) {
- chargesPayment.updateLoan(loan);
- final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge,
- chargesPayment.getAmount(loan.getCurrency()).getAmount(), installmentNumber);
- chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
- loan.addLoanTransaction(chargesPayment);
- loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, loan);
-
- final List chargePaymentInstallments = new ArrayList<>();
- List installments = loan.getRepaymentScheduleInstallments();
- int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
- .fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
- for (final LoanRepaymentScheduleInstallment installment : installments) {
- boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
- if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null
- && charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) {
- chargePaymentInstallments.add(installment);
- break;
- }
- }
- final Set loanCharges = new HashSet<>(1);
- loanCharges.add(charge);
- loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment,
- new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges,
- new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
-
- loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate());
- }
-
/**
* Creates a loanTransaction for "Apply Charge Event" with transaction date set to "suppliedTransactionDate". The
* newly created transaction is also added to the Loan on which this method is called.
@@ -230,14 +182,14 @@ public class LoanChargeService {
if (loanCharge.getChargeCalculation().isPercentageBased()) {
chargeAmt = loanCharge.getPercentage();
if (loanCharge.isInstalmentFee()) {
- totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
+ totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
} else if (loanCharge.isOverdueInstallmentCharge()) {
totalChargeAmt = loanCharge.amountOutstanding();
}
} else {
chargeAmt = loanCharge.amountOrPercentage();
}
- loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
+ update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(), totalChargeAmt);
// NOTE: must add new loan charge to set of loan charges before
// reprocessing the repayment schedule.
@@ -250,72 +202,11 @@ public class LoanChargeService {
// store Id's of existing loan transactions and existing reversed loan transactions
final SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loanCharge);
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_ADDED, loan);
}
- public void updateLoanCharges(final Loan loan, final Set loanCharges) {
- List existingCharges = fetchAllLoanChargeIds(loan);
-
- /* Process new and updated charges **/
- for (final LoanCharge loanCharge : loanCharges) {
- LoanCharge charge = loanCharge;
- // add new charges
- if (loanCharge.getId() == null) {
- LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
- loanCharge.update(loan);
- if (loan.getLoanProduct().isMultiDisburseLoan() && loanCharge.isTrancheDisbursementCharge()) {
- loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(loan);
- for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
- if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getId() == null
- && loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().equals(loanDisbursementDetails)) {
- loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, loanDisbursementDetails);
- loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
- }
- }
- }
- loan.addCharge(loanCharge);
-
- } else {
- charge = fetchLoanChargesById(loan, charge.getId());
- if (charge != null) {
- existingCharges.remove(charge.getId());
- }
- }
- final BigDecimal amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
- BigDecimal chargeAmt;
- BigDecimal totalChargeAmt = BigDecimal.ZERO;
- if (loanCharge.getChargeCalculation().isPercentageBased()) {
- chargeAmt = loanCharge.getPercentage();
- if (loanCharge.isInstalmentFee()) {
- totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
- }
- } else {
- chargeAmt = loanCharge.amountOrPercentage();
- }
- if (charge != null) {
- charge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
- totalChargeAmt);
- }
-
- }
-
- /* Updated deleted charges **/
- for (Long id : existingCharges) {
- fetchLoanChargesById(loan, id).setActive(false);
- }
- loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
- }
-
- private List fetchAllLoanChargeIds(final Loan loan) {
- List list = new ArrayList<>();
- for (LoanCharge loanCharge : loan.getCharges()) {
- list.add(loanCharge.getId());
- }
- return list;
- }
-
public BigDecimal calculateAmountPercentageAppliedTo(final Loan loan, final LoanCharge loanCharge) {
if (loanCharge.isOverdueInstallmentCharge()) {
return loanCharge.getAmountPercentageAppliedTo();
@@ -343,38 +234,254 @@ public class LoanChargeService {
};
}
- private BigDecimal getDerivedAmountForCharge(final Loan loan, final LoanCharge loanCharge) {
- BigDecimal amount = BigDecimal.ZERO;
- if (loan.isMultiDisburmentLoan() && loanCharge.getCharge().getChargeTimeType().equals(ChargeTimeType.DISBURSEMENT.getValue())) {
- amount = loan.getApprovedPrincipal();
- } else {
- // If charge type is specified due date and loan is multi disburment loan.
- // Then we need to get as of this loan charge due date how much amount disbursed.
- if (loanCharge.isSpecifiedDueDate() && loan.isMultiDisburmentLoan()) {
- for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
- if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
- amount = amount.add(loanDisbursementDetails.principal());
+ public void updateLoanCharges(final Loan loan, final Set loanCharges) {
+ List existingCharges = fetchAllLoanChargeIds(loan);
+
+ /* Process new and updated charges **/
+ for (final LoanCharge loanCharge : loanCharges) {
+ LoanCharge charge = loanCharge;
+ // add new charges
+ if (loanCharge.getId() == null) {
+ LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
+ loanCharge.update(loan);
+ if (loan.getLoanProduct().isMultiDisburseLoan() && loanCharge.isTrancheDisbursementCharge()) {
+ loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().updateLoan(loan);
+ for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
+ if (loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().getId() == null
+ && loanCharge.getTrancheDisbursementCharge().getloanDisbursementDetails().equals(loanDisbursementDetails)) {
+ loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge, loanDisbursementDetails);
+ loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
+ }
}
}
+ loan.getLoanCharges().add(loanCharge);
} else {
- amount = loan.getPrincipal().getAmount();
+ charge = loan.fetchLoanChargesById(charge.getId());
+ if (charge != null) {
+ existingCharges.remove(charge.getId());
+ }
+ }
+ final BigDecimal amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
+ BigDecimal chargeAmt;
+ BigDecimal totalChargeAmt = BigDecimal.ZERO;
+ if (loanCharge.getChargeCalculation().isPercentageBased()) {
+ chargeAmt = loanCharge.getPercentage();
+ if (loanCharge.isInstalmentFee()) {
+ totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
+ }
+ } else {
+ chargeAmt = loanCharge.amountOrPercentage();
+ }
+ if (charge != null) {
+ update(charge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
+ totalChargeAmt);
}
}
- return amount;
+
+ /* Updated deleted charges **/
+ for (Long id : existingCharges) {
+ loan.fetchLoanChargesById(id).setActive(false);
+ }
+ loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
}
- private Money getTotalAllTrancheDisbursementAmount(final Loan loan) {
+ public BigDecimal calculatePerInstallmentChargeAmount(final Loan loan, final ChargeCalculationType calculationType,
+ final BigDecimal percentage) {
Money amount = Money.zero(loan.getCurrency());
- if (loan.isMultiDisburmentLoan()) {
- for (final LoanDisbursementDetails loanDisbursementDetail : loan.getDisbursementDetails()) {
- amount = amount.plus(loanDisbursementDetail.principal());
- }
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment installment : installments) {
+ amount = amount.plus(calculateInstallmentChargeAmount(loan, calculationType, percentage, installment));
}
- return amount;
+ return amount.getAmount();
}
- private BigDecimal calculatePerInstallmentChargeAmount(final LoanCharge loanCharge) {
- return loanCharge.calculatePerInstallmentChargeAmount();
+ public Map update(final JsonCommand command, final BigDecimal amount, final LoanCharge loanCharge) {
+ final Map actualChanges = new LinkedHashMap<>(7);
+
+ final String dateFormatAsInput = command.dateFormat();
+ final String localeAsInput = command.locale();
+
+ final String dueDateParamName = "dueDate";
+ if (command.isChangeInLocalDateParameterNamed(dueDateParamName, loanCharge.getDueLocalDate())) {
+ final String valueAsInput = command.stringValueOfParameterNamed(dueDateParamName);
+ actualChanges.put(dueDateParamName, valueAsInput);
+ actualChanges.put("dateFormat", dateFormatAsInput);
+ actualChanges.put("locale", localeAsInput);
+
+ loanCharge.setDueDate(command.localDateValueOfParameterNamed(dueDateParamName));
+ }
+
+ final String amountParamName = "amount";
+ if (command.isChangeInBigDecimalParameterNamed(amountParamName, loanCharge.getAmount())) {
+ final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(amountParamName);
+ BigDecimal loanChargeAmount;
+ actualChanges.put(amountParamName, newValue);
+ actualChanges.put("locale", localeAsInput);
+ switch (loanCharge.getChargeCalculation()) {
+ case INVALID:
+ break;
+ case FLAT:
+ if (loanCharge.isInstalmentFee()) {
+ loanCharge.setAmount(
+ newValue.multiply(BigDecimal.valueOf(loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions())));
+ } else {
+ loanCharge.setAmount(newValue);
+ }
+ loanCharge.setAmountOutstanding(loanCharge.calculateOutstanding());
+ break;
+ case PERCENT_OF_AMOUNT:
+ case PERCENT_OF_AMOUNT_AND_INTEREST:
+ case PERCENT_OF_INTEREST:
+ case PERCENT_OF_DISBURSEMENT_AMOUNT:
+ loanCharge.setPercentage(newValue);
+ loanCharge.setAmountPercentageAppliedTo(amount);
+ loanChargeAmount = BigDecimal.ZERO;
+ if (loanCharge.isInstalmentFee()) {
+ loanChargeAmount = calculatePerInstallmentChargeAmount(loanCharge.getLoan(), loanCharge.getChargeCalculation(),
+ loanCharge.getPercentage());
+ }
+ if (loanChargeAmount.compareTo(BigDecimal.ZERO) == 0) {
+ loanChargeAmount = loanCharge.percentageOf(loanCharge.getAmountPercentageAppliedTo());
+ }
+ loanCharge.setAmount(loanCharge.minimumAndMaximumCap(loanChargeAmount));
+ loanCharge.setAmountOutstanding(loanCharge.calculateOutstanding());
+ break;
+ }
+ loanCharge.setAmountOrPercentage(newValue);
+ if (loanCharge.isInstalmentFee()) {
+ updateInstallmentCharges(loanCharge);
+ }
+ }
+ return actualChanges;
+ }
+
+ public void populateDerivedFields(final LoanCharge loanCharge, final BigDecimal amountPercentageAppliedTo,
+ final BigDecimal chargeAmount, Integer numberOfRepayments, BigDecimal loanChargeAmount) {
+ switch (loanCharge.getChargeCalculation()) {
+ case INVALID:
+ loanCharge.setPercentage(null);
+ loanCharge.setAmount(null);
+ loanCharge.setAmountPercentageAppliedTo(null);
+ loanCharge.setAmountPaid(null);
+ loanCharge.setAmountOutstanding(BigDecimal.ZERO);
+ loanCharge.setAmountWaived(null);
+ loanCharge.setAmountWrittenOff(null);
+ break;
+ case FLAT:
+ loanCharge.setPercentage(null);
+ loanCharge.setAmountPercentageAppliedTo(null);
+ loanCharge.setAmountPaid(null);
+ if (loanCharge.isInstalmentFee()) {
+ if (numberOfRepayments == null) {
+ numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions();
+ }
+ loanCharge.setAmount(chargeAmount.multiply(BigDecimal.valueOf(numberOfRepayments)));
+ } else {
+ loanCharge.setAmount(chargeAmount);
+ }
+ loanCharge.setAmountOutstanding(loanCharge.getAmount());
+ loanCharge.setAmountWaived(null);
+ loanCharge.setAmountWrittenOff(null);
+ break;
+ case PERCENT_OF_AMOUNT:
+ case PERCENT_OF_AMOUNT_AND_INTEREST:
+ case PERCENT_OF_INTEREST:
+ case PERCENT_OF_DISBURSEMENT_AMOUNT:
+ loanCharge.setPercentage(chargeAmount);
+ loanCharge.setAmountPercentageAppliedTo(amountPercentageAppliedTo);
+ if (loanChargeAmount.compareTo(BigDecimal.ZERO) == 0) {
+ loanChargeAmount = loanCharge.percentageOf(loanCharge.getAmountPercentageAppliedTo());
+ }
+ loanCharge.setAmount(loanCharge.minimumAndMaximumCap(loanChargeAmount));
+ loanCharge.setAmountPaid(null);
+ loanCharge.setAmountOutstanding(loanCharge.calculateOutstanding());
+ loanCharge.setAmountWaived(null);
+ loanCharge.setAmountWrittenOff(null);
+ break;
+ }
+ loanCharge.setAmountOrPercentage(chargeAmount);
+ if (loanCharge.getLoan() != null && loanCharge.isInstalmentFee()) {
+ updateInstallmentCharges(loanCharge);
+ }
+ }
+
+ public void update(final LoanCharge loanCharge, final BigDecimal amount, final LocalDate dueDate, final Integer numberOfRepayments) {
+ BigDecimal amountPercentageAppliedTo = BigDecimal.ZERO;
+ if (loanCharge.getLoan() != null) {
+ switch (loanCharge.getChargeCalculation()) {
+ case PERCENT_OF_AMOUNT:
+ // If charge type is specified due date and loan is multi disburment loan.
+ // Then we need to get as of this loan charge due date how much amount disbursed.
+ if (loanCharge.getLoan().isMultiDisburmentLoan() && loanCharge.isSpecifiedDueDate()) {
+ for (final LoanDisbursementDetails loanDisbursementDetails : loanCharge.getLoan().getDisbursementDetails()) {
+ if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
+ amountPercentageAppliedTo = amountPercentageAppliedTo.add(loanDisbursementDetails.principal());
+ }
+ }
+ } else {
+ amountPercentageAppliedTo = loanCharge.getLoan().getPrincipal().getAmount();
+ }
+ break;
+ case PERCENT_OF_AMOUNT_AND_INTEREST:
+ amountPercentageAppliedTo = loanCharge.getLoan().getPrincipal().getAmount()
+ .add(loanCharge.getLoan().getTotalInterest());
+ break;
+ case PERCENT_OF_INTEREST:
+ amountPercentageAppliedTo = loanCharge.getLoan().getTotalInterest();
+ break;
+ case PERCENT_OF_DISBURSEMENT_AMOUNT:
+ LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = loanCharge.getLoanTrancheDisbursementCharge();
+ amountPercentageAppliedTo = loanTrancheDisbursementCharge.getloanDisbursementDetails().principal();
+ break;
+ default:
+ break;
+ }
+ }
+ update(loanCharge, amount, dueDate, amountPercentageAppliedTo, numberOfRepayments, BigDecimal.ZERO);
+ }
+
+ public LoanCharge create(final Loan loan, final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
+ final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
+ final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final BigDecimal loanChargeAmount,
+ final ExternalId externalId) {
+ final LoanCharge loanCharge = new LoanCharge();
+ loanCharge.setLoan(loan);
+ loanCharge.setCharge(chargeDefinition);
+ loanCharge.setSubmittedOnDate(DateUtils.getBusinessLocalDate());
+ loanCharge.setPenaltyCharge(chargeDefinition.isPenalty());
+ loanCharge.setMinCap(chargeDefinition.getMinCap());
+ loanCharge.setMaxCap(chargeDefinition.getMaxCap());
+ loanCharge.setChargeTime(chargeTime == null ? chargeDefinition.getChargeTimeType() : chargeTime.getValue());
+
+ if (loanCharge.getChargeTimeType().equals(ChargeTimeType.SPECIFIED_DUE_DATE)
+ || loanCharge.getChargeTimeType().equals(ChargeTimeType.OVERDUE_INSTALLMENT)) {
+
+ if (dueDate == null) {
+ final String defaultUserMessage = "Loan charge is missing due date.";
+ throw new LoanChargeWithoutMandatoryFieldException("loanChargeAmount", "dueDate", defaultUserMessage,
+ chargeDefinition.getId(), chargeDefinition.getName());
+ }
+
+ loanCharge.setDueDate(dueDate);
+ } else {
+ loanCharge.setDueDate(null);
+ }
+
+ loanCharge.setChargeCalculation(chargeCalculation == null ? chargeDefinition.getChargeCalculation() : chargeCalculation.getValue());
+
+ BigDecimal chargeAmount = chargeDefinition.getAmount();
+ if (amount != null) {
+ chargeAmount = amount;
+ }
+
+ loanCharge.setChargePaymentMode(chargePaymentMode == null ? chargeDefinition.getChargePaymentMode() : chargePaymentMode.getValue());
+
+ populateDerivedFields(loanCharge, loanPrincipal, chargeAmount, numberOfRepayments, loanChargeAmount);
+
+ loanCharge.setPaid(loanCharge.determineIfFullyPaid());
+ loanCharge.setExternalId(externalId);
+
+ return loanCharge;
}
public LoanCharge fetchLoanChargesById(final Loan loan, final Long id) {
@@ -402,4 +509,246 @@ public class LoanChargeService {
}
}
+ private void clearLoanInstallmentChargesBeforeRegeneration(final LoanCharge loanCharge) {
+ /*
+ * JW https://issues.apache.org/jira/browse/FINERACT-1557 For loan installment charges only : Clear down
+ * installment charges from the loanCharge and from each of the repayment installments and allow them to be
+ * recalculated fully anew. This patch is to avoid the 'merging' of existing and regenerated installment charges
+ * which results in the installment charges being deleted on loan approval if the schedule is regenerated. Not
+ * pretty. updateInstallmentCharges in LoanCharge.java: the merging looks like it will work but doesn't so this
+ * patch simply hits the part which 'adds all' rather than merge. Possibly an ORM issue. The issue could be to
+ * do with the fact that, on approval, the "recalculateLoanCharge" happens twice (probably 2 schedule
+ * regenerations) whereas it only happens once on Submit and Disburse (and no problems with them)
+ *
+ * if (this.loanInstallmentCharge.isEmpty()) { this.loanInstallmentCharge.addAll(newChargeInstallments);
+ */
+ Loan loan = loanCharge.getLoan();
+ if (!loan.isSubmittedAndPendingApproval() && !loan.isApproved()) {
+ return;
+ } // doing for both just in case status is not
+ // updated at this points
+ if (loanCharge.isInstalmentFee()) {
+ loanCharge.clearLoanInstallmentCharges();
+ for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
+ if (installment.isRecalculatedInterestComponent()) {
+ continue; // JW: does this in generateInstallmentLoanCharges - but don't understand it
+ }
+ installment.getInstallmentCharges().clear();
+ }
+ }
+ }
+
+ private void handleChargePaidTransaction(final Loan loan, final LoanCharge charge, final LoanTransaction chargesPayment,
+ final Integer installmentNumber) {
+ chargesPayment.updateLoan(loan);
+ final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(chargesPayment, charge,
+ chargesPayment.getAmount(loan.getCurrency()).getAmount(), installmentNumber);
+ chargesPayment.getLoanChargesPaid().add(loanChargePaidBy);
+ loan.addLoanTransaction(chargesPayment);
+ loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_PAYMENT, loan);
+
+ final List chargePaymentInstallments = new ArrayList<>();
+ List installments = loan.getRepaymentScheduleInstallments();
+ int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper
+ .fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
+ for (final LoanRepaymentScheduleInstallment installment : installments) {
+ boolean isFirstInstallment = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
+ if (installment.getInstallmentNumber().equals(installmentNumber) || (installmentNumber == null
+ && charge.isDueInPeriod(installment.getFromDate(), installment.getDueDate(), isFirstInstallment))) {
+ chargePaymentInstallments.add(installment);
+ break;
+ }
+ }
+ final Set loanCharges = new HashSet<>(1);
+ loanCharges.add(charge);
+ loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargesPayment,
+ new TransactionCtx(loan.getCurrency(), chargePaymentInstallments, loanCharges,
+ new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
+
+ loanLifecycleStateMachine.determineAndTransition(loan, chargesPayment.getTransactionDate());
+ }
+
+ private BigDecimal calculatePerInstallmentChargeAmount(final Loan loan, final LoanCharge loanCharge) {
+ return calculatePerInstallmentChargeAmount(loan, loanCharge.getChargeCalculation(), loanCharge.getPercentage());
+ }
+
+ private void updateInstallmentCharges(final LoanCharge loanCharge) {
+ final Collection remove = new HashSet<>();
+ final List newChargeInstallments = generateInstallmentLoanCharges(loanCharge.getLoan(), loanCharge);
+ if (loanCharge.getLoanInstallmentCharge().isEmpty()) {
+ loanCharge.getLoanInstallmentCharge().addAll(newChargeInstallments);
+ } else {
+ int index = 0;
+ final List oldChargeInstallments = new ArrayList<>();
+ if (loanCharge.getLoanInstallmentCharge() != null && !loanCharge.getLoanInstallmentCharge().isEmpty()) {
+ oldChargeInstallments.addAll(loanCharge.getLoanInstallmentCharge());
+ }
+ Collections.sort(oldChargeInstallments);
+ final LoanInstallmentCharge[] loanChargePerInstallmentArray = newChargeInstallments
+ .toArray(new LoanInstallmentCharge[newChargeInstallments.size()]);
+ for (final LoanInstallmentCharge chargePerInstallment : oldChargeInstallments) {
+ if (index == loanChargePerInstallmentArray.length) {
+ remove.add(chargePerInstallment);
+ chargePerInstallment.getInstallment().getInstallmentCharges().remove(chargePerInstallment);
+ } else {
+ LoanInstallmentCharge newLoanInstallmentCharge = loanChargePerInstallmentArray[index++];
+ newLoanInstallmentCharge.getInstallment().getInstallmentCharges().remove(newLoanInstallmentCharge);
+ chargePerInstallment.copyFrom(newLoanInstallmentCharge);
+ }
+ }
+ loanCharge.getLoanInstallmentCharge().removeAll(remove);
+ while (index < loanChargePerInstallmentArray.length) {
+ loanCharge.getLoanInstallmentCharge().add(loanChargePerInstallmentArray[index++]);
+ }
+ }
+ Money amount = Money.zero(loanCharge.getLoan().getCurrency());
+ for (LoanInstallmentCharge charge : loanCharge.getLoanInstallmentCharge()) {
+ amount = amount.plus(charge.getAmount());
+ }
+ loanCharge.setAmount(amount.getAmount());
+ }
+
+ private List generateInstallmentLoanCharges(final Loan loan, final LoanCharge loanCharge) {
+ final List loanChargePerInstallments = new ArrayList<>();
+ if (loanCharge.isInstalmentFee()) {
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment installment : installments) {
+ if (installment.isRecalculatedInterestComponent()) {
+ continue;
+ }
+ BigDecimal amount;
+ if (loanCharge.getChargeCalculation().isFlat()) {
+ amount = loanCharge.amountOrPercentage();
+ } else {
+ amount = calculateInstallmentChargeAmount(loan, loanCharge.getChargeCalculation(), loanCharge.getPercentage(),
+ installment).getAmount();
+ }
+ final LoanInstallmentCharge loanInstallmentCharge = new LoanInstallmentCharge(amount, loanCharge, installment);
+ installment.getInstallmentCharges().add(loanInstallmentCharge);
+ loanChargePerInstallments.add(loanInstallmentCharge);
+ }
+ }
+ return loanChargePerInstallments;
+ }
+
+ private BigDecimal calculateOverdueAmountPercentageAppliedTo(final Loan loan, final LoanCharge loanCharge,
+ final int penaltyWaitPeriod) {
+ LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment();
+ LocalDate graceDate = DateUtils.getBusinessLocalDate().minusDays(penaltyWaitPeriod);
+ Money amount = Money.zero(loan.getCurrency());
+
+ if (DateUtils.isAfter(graceDate, installment.getDueDate())) {
+ amount = calculateOverdueAmountPercentageAppliedTo(loan, installment, loanCharge.getChargeCalculation());
+ if (!amount.isGreaterThanZero()) {
+ loanCharge.setActive(false);
+ }
+ } else {
+ loanCharge.setActive(false);
+ }
+ return amount.getAmount();
+ }
+
+ private Money calculateOverdueAmountPercentageAppliedTo(final Loan loan, final LoanRepaymentScheduleInstallment installment,
+ final ChargeCalculationType calculationType) {
+ return switch (calculationType) {
+ case PERCENT_OF_AMOUNT -> installment.getPrincipalOutstanding(loan.getCurrency());
+ case PERCENT_OF_AMOUNT_AND_INTEREST ->
+ installment.getPrincipalOutstanding(loan.getCurrency()).plus(installment.getInterestOutstanding(loan.getCurrency()));
+ case PERCENT_OF_INTEREST -> installment.getInterestOutstanding(loan.getCurrency());
+ default -> Money.zero(loan.getCurrency());
+ };
+ }
+
+ private void update(final LoanCharge loanCharge, final BigDecimal amount, final LocalDate dueDate, final BigDecimal loanPrincipal,
+ Integer numberOfRepayments, BigDecimal loanChargeAmount) {
+ if (dueDate != null) {
+ loanCharge.setDueDate(dueDate);
+ }
+
+ if (amount != null) {
+ switch (loanCharge.getChargeCalculation()) {
+ case INVALID:
+ break;
+ case FLAT:
+ if (loanCharge.isInstalmentFee()) {
+ if (numberOfRepayments == null) {
+ numberOfRepayments = loanCharge.getLoan().fetchNumberOfInstallmensAfterExceptions();
+ }
+ loanCharge.setAmount(amount.multiply(BigDecimal.valueOf(numberOfRepayments)));
+ } else {
+ loanCharge.setAmount(amount);
+ }
+ break;
+ case PERCENT_OF_AMOUNT:
+ case PERCENT_OF_AMOUNT_AND_INTEREST:
+ case PERCENT_OF_INTEREST:
+ case PERCENT_OF_DISBURSEMENT_AMOUNT:
+ loanCharge.setPercentage(amount);
+ loanCharge.setAmountPercentageAppliedTo(loanPrincipal);
+ if (loanChargeAmount.compareTo(BigDecimal.ZERO) == 0) {
+ loanChargeAmount = loanCharge.percentageOf(loanCharge.getAmountPercentageAppliedTo());
+ }
+ loanCharge.setAmount(loanCharge.minimumAndMaximumCap(loanChargeAmount));
+ break;
+ }
+ loanCharge.setAmountOrPercentage(amount);
+ loanCharge.setAmountOutstanding(loanCharge.calculateOutstanding());
+ if (loanCharge.getLoan() != null && loanCharge.isInstalmentFee()) {
+ updateInstallmentCharges(loanCharge);
+ }
+ }
+ }
+
+ private Money getTotalAllTrancheDisbursementAmount(final Loan loan) {
+ Money amount = Money.zero(loan.getCurrency());
+ if (loan.isMultiDisburmentLoan()) {
+ for (final LoanDisbursementDetails loanDisbursementDetail : loan.getDisbursementDetails()) {
+ amount = amount.plus(loanDisbursementDetail.principal());
+ }
+ }
+ return amount;
+ }
+
+ private List fetchAllLoanChargeIds(final Loan loan) {
+ List list = new ArrayList<>();
+ for (LoanCharge loanCharge : loan.getLoanCharges()) {
+ list.add(loanCharge.getId());
+ }
+ return list;
+ }
+
+ private Money calculateInstallmentChargeAmount(final Loan loan, final ChargeCalculationType calculationType,
+ final BigDecimal percentage, final LoanRepaymentScheduleInstallment installment) {
+ Money percentOf = switch (calculationType) {
+ case PERCENT_OF_AMOUNT -> installment.getPrincipal(loan.getCurrency());
+ case PERCENT_OF_AMOUNT_AND_INTEREST ->
+ installment.getPrincipal(loan.getCurrency()).plus(installment.getInterestCharged(loan.getCurrency()));
+ case PERCENT_OF_INTEREST -> installment.getInterestCharged(loan.getCurrency());
+ case PERCENT_OF_DISBURSEMENT_AMOUNT, INVALID, FLAT -> Money.zero(loan.getCurrency());
+
+ };
+ return Money.zero(loan.getCurrency()) //
+ .plus(LoanCharge.percentageOf(percentOf.getAmount(), percentage));
+ }
+
+ private BigDecimal getDerivedAmountForCharge(final Loan loan, final LoanCharge loanCharge) {
+ BigDecimal amount = BigDecimal.ZERO;
+ if (loan.isMultiDisburmentLoan() && loanCharge.getCharge().getChargeTimeType().equals(ChargeTimeType.DISBURSEMENT.getValue())) {
+ amount = loan.getApprovedPrincipal();
+ } else {
+ // If charge type is specified due date and loan is multi disburment loan.
+ // Then we need to get as of this loan charge due date how much amount disbursed.
+ if (loanCharge.isSpecifiedDueDate() && loan.isMultiDisburmentLoan()) {
+ for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
+ if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
+ amount = amount.add(loanDisbursementDetails.principal());
+ }
+ }
+ } else {
+ amount = loan.getPrincipal().getAmount();
+ }
+ }
+ return amount;
+ }
+
}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
index 4595f6e04..c7cd77ae7 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImpl.java
@@ -62,6 +62,8 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
+ private final LoanBalanceService loanBalanceService;
+ private final LoanTransactionService loanTransactionService;
@Override
public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command,
@@ -91,7 +93,8 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
loanTransaction.updateLoan(loan);
- final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
+ final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
+ loanTransaction);
if (loanTransaction.isNotZero()) {
loan.addLoanTransaction(loanTransaction);
@@ -128,6 +131,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
.fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate());
boolean reprocessOnPostConditions = false;
+
boolean processLatest = isTransactionChronologicallyLatest //
&& adjustedTransaction == null // covers reversals
&& !loan.isForeclosure() //
@@ -163,7 +167,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
if (loanTransaction.isNotRecoveryRepayment()) {
loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate());
} else {
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
if (loan.getLoanProduct().isMultiDisburseLoan()) {
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionService.java
new file mode 100644
index 000000000..bc3e2f582
--- /dev/null
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionService.java
@@ -0,0 +1,58 @@
+/**
+ * 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.time.LocalDate;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class LoanTransactionService {
+
+ private final LoanTransactionRepository loanTransactionRepository;
+
+ public List retrieveListOfTransactionsForReprocessing(final Loan loan) {
+ return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
+ .sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
+ }
+
+ public boolean isChronologicallyLatestRepaymentOrWaiver(final Loan loan, final LoanTransaction loanTransaction) {
+ final Optional lastTransactionDateForReprocessing = loanTransactionRepository
+ .findLastTransactionDateForReprocessing(loan);
+
+ return lastTransactionDateForReprocessing.isEmpty()
+ || !DateUtils.isAfter(lastTransactionDateForReprocessing.get(), loanTransaction.getTransactionDate());
+ }
+
+ private Predicate loanTransactionForReprocessingPredicate() {
+ return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
+ || transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
+ }
+
+}
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/schedule/LoanScheduleComponent.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/schedule/LoanScheduleComponent.java
index 48029d58a..4688594c7 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/schedule/LoanScheduleComponent.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/schedule/LoanScheduleComponent.java
@@ -25,12 +25,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class LoanScheduleComponent {
+ private final LoanBalanceService loanBalanceService;
+
public void updateLoanSchedule(Loan loan, final LoanScheduleModel modifiedLoanSchedule) {
final List periods = modifiedLoanSchedule.getPeriods();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
@@ -55,7 +58,7 @@ public class LoanScheduleComponent {
loan.getRepaymentScheduleInstallments().removeIf(i -> !existInstallment(periods, i.getInstallmentNumber()));
loan.updateLoanScheduleDependentDerivedFields();
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
public void updateLoanSchedule(Loan loan, final List installments) {
@@ -72,7 +75,7 @@ public class LoanScheduleComponent {
loan.getRepaymentScheduleInstallments().removeIf(i -> !existInstallment(installments, i.getInstallmentNumber()));
loan.updateLoanScheduleDependentDerivedFields();
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private LoanRepaymentScheduleInstallment findByInstallmentNumber(final Collection installments,
diff --git a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java
index 2e8203134..290b3e733 100644
--- a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java
+++ b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/SingleLoanChargeRepaymentScheduleProcessingWrapperTest.java
@@ -43,6 +43,10 @@ import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
@@ -64,6 +68,9 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
private ArgumentCaptor penaltyChargesWaived = ArgumentCaptor.forClass(Money.class);
private ArgumentCaptor penaltyChargesWrittenOff = ArgumentCaptor.forClass(Money.class);
+ private final LoanChargeService loanChargeService = new LoanChargeService(mock(LoanChargeValidator.class),
+ mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class));
+
@BeforeAll
public static void init() {
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
@@ -141,7 +148,8 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
when(charge.isPenalty()).thenReturn(penalty);
Loan loan = mock(Loan.class);
when(loan.isInterestBearing()).thenReturn(false);
- return new LoanCharge(loan, charge, new BigDecimal(1000), new BigDecimal(10), ChargeTimeType.SPECIFIED_DUE_DATE,
+
+ return loanChargeService.create(loan, charge, new BigDecimal(1000), new BigDecimal(10), ChargeTimeType.SPECIFIED_DUE_DATE,
ChargeCalculationType.FLAT, LocalDate.of(2023, 1, 15), ChargePaymentMode.REGULAR, 1, null, null);
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
index c2fffc12c..fcfa66de1 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java
@@ -90,7 +90,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Abs
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
@@ -118,13 +121,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
private final LoanRepositoryWrapper loanRepositoryWrapper;
private final InterestRefundService interestRefundService;
private final LoanScheduleComponent loanSchedule;
+ private final LoanTransactionService loanTransactionService;
- public AdvancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator, LoanRepositoryWrapper loanRepositoryWrapper,
- InterestRefundService interestRefundService, ExternalIdFactory externalIdFactory, LoanScheduleComponent loanSchedule) {
- super(externalIdFactory);
+ public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper,
+ final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory,
+ final LoanScheduleComponent loanSchedule, final LoanTransactionService loanTransactionRepository,
+ final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
+ super(externalIdFactory, loanChargeValidator, loanBalanceService);
this.emiCalculator = emiCalculator;
this.loanRepositoryWrapper = loanRepositoryWrapper;
this.interestRefundService = interestRefundService;
+ this.loanTransactionService = loanTransactionRepository;
this.loanSchedule = loanSchedule;
}
@@ -269,7 +276,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
- List transactions = loan.retrieveListOfTransactionsForReprocessing();
+ final List transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
List installments = loan.getRepaymentScheduleInstallments();
Set charges = loan.getActiveCharges();
@@ -1514,7 +1521,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
newTransaction.updateLoan(loan);
loan.getLoanTransactions().add(newTransaction);
}
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
}
}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
index dac63b237..7485d1411 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/InternalProgressiveLoanApiResource.java
@@ -62,6 +62,7 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
private final InterestScheduleModelRepositoryWrapper writePlatformService;
+ private final LoanTransactionService loanTransactionService;
@Override
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
@@ -94,7 +95,7 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
}
private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) {
- final List transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing();
+ final List transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
final Pair changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
index ea8a7f4f7..46b4243cb 100644
--- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
+++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessorTest.java
@@ -49,6 +49,7 @@ import java.util.Set;
import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -69,6 +70,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.InterestRefundService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
@@ -98,8 +103,6 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
private static final MockedStatic MONEY_HELPER = mockStatic(MoneyHelper.class);
private AdvancedPaymentScheduleTransactionProcessor underTest;
private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
- private static final LoanRepositoryWrapper loanRepositoryWrapper = Mockito.mock(LoanRepositoryWrapper.class);
- private static final LoanScheduleComponent loanSchedule = Mockito.mock(LoanScheduleComponent.class);
@BeforeAll
public static void init() {
@@ -114,7 +117,10 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
@BeforeEach
public void setUp() {
- underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null, null, loanSchedule);
+ underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, Mockito.mock(LoanRepositoryWrapper.class),
+ Mockito.mock(InterestRefundService.class), Mockito.mock(ExternalIdFactory.class), Mockito.mock(LoanScheduleComponent.class),
+ Mockito.mock(LoanTransactionService.class), Mockito.mock(LoanChargeValidator.class),
+ Mockito.mock(LoanBalanceService.class));
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
index 83edbd7e3..081853410 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAccountDomainServiceJpa.java
@@ -104,11 +104,13 @@ import org.apache.fineract.portfolio.loanaccount.service.InterestRefundServiceDe
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
@@ -147,7 +149,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
- private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
private final LoanTransactionValidator loanTransactionValidator;
private final LoanForeclosureValidator loanForeclosureValidator;
@@ -161,6 +162,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
+ private final LoanBalanceService loanBalanceService;
+ private final LoanTransactionService loanTransactionService;
@Transactional
@Override
@@ -767,7 +770,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
- final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
+ final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
loanAccrualsProcessingService.processAccrualsOnLoanForeClosure(loan, foreClosureDate, newTransactions);
@@ -875,7 +878,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
LoanTransaction refundTransaction = LoanTransaction.refund(loan, loanTransactionType, transactionAmount, paymentDetail,
transactionDate, txnExternalId);
- final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
+ final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
+ refundTransaction);
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
@@ -1002,7 +1006,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final List newInstallments = new ArrayList<>(loan.getRepaymentScheduleInstallments());
final MonetaryCurrency currency = loan.getCurrency();
Money totalPrincipal = Money.zero(currency);
- final Money[] balances = loan.retrieveIncomeForOverlappingPeriod(transactionDate);
+ final Money[] balances = loanBalanceService.retrieveIncomeForOverlappingPeriod(loan, transactionDate);
boolean isInterestComponent = true;
for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) {
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 54003773d..7afe2bb2c 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
@@ -1571,7 +1571,8 @@ public class LoanScheduleAssembler {
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod instanceof LoanScheduleModelDisbursementPeriod) {
for (final LoanCharge loanCharge : nonCompoundingCharges) {
- loanCharge.populateDerivedFields(totalInterest, loanCharge.amountOrPercentage(), null, BigDecimal.ZERO);
+ loanChargeService.populateDerivedFields(loanCharge, totalInterest, loanCharge.amountOrPercentage(), null,
+ BigDecimal.ZERO);
loanScheduleModelPeriod.addLoanCharges(loanCharge.getAmountOutstanding(), BigDecimal.ZERO);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
index ea0417740..58c2927f0 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java
@@ -55,6 +55,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
private final BusinessEventNotifierService businessEventNotifierService;
private final LoanTransactionAssembler loanTransactionAssembler;
private final LoanAccountService loanAccountService;
+ private final LoanBalanceService loanBalanceService;
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
@@ -93,7 +94,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
return;
}
- LocalDate closureDate = loan.isOverPaid() ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
+ LocalDate closureDate = loanBalanceService.isOverPaid(loan) ? loan.getOverpaidOnDate() : loan.getClosedOnDate();
// Reverse accrual activities posted after the closure date
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(closureDate))
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
index 976835c2a..b50865a53 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
@@ -113,6 +113,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
private final TransactionTemplate transactionTemplate;
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanChargeService loanChargeService;
+ private final LoanBalanceService loanBalanceService;
/**
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
@@ -307,7 +308,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
// TODO implement progressive accrual case
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
- final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
+ final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
MonetaryCurrency currency = loan.getCurrency();
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
@@ -494,7 +495,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
Money interest = null;
final boolean isPastPeriod = isAfterPeriod(tillDate, installment);
final boolean isInPeriod = isInPeriod(tillDate, installment, false);
- if (isPastPeriod || loan.isClosed() || loan.isOverPaid()) {
+ if (isPastPeriod || loan.isClosed() || loanBalanceService.isOverPaid(loan)) {
interest = installment.getInterestCharged(currency).minus(installment.getCreditedInterest());
} else {
if (isInPeriod) { // first period first day is not accrued
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java
index 98ff8f82e..9ad6a79aa 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeAssembler.java
@@ -61,6 +61,7 @@ public class LoanChargeAssembler {
private final LoanChargeRepository loanChargeRepository;
private final LoanProductRepository loanProductRepository;
private final ExternalIdFactory externalIdFactory;
+ private final LoanChargeService loanChargeService;
public Set fromParsedJson(final JsonElement element, List disbursementDetails) {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
@@ -210,7 +211,7 @@ public class LoanChargeAssembler {
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
- loanCharge.update(amount, dueDate, numberOfRepayments);
+ loanChargeService.update(loanCharge, amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
@@ -296,7 +297,7 @@ public class LoanChargeAssembler {
if (percentage == null) {
percentage = chargeDefinition.getAmount();
}
- loanCharge = LoanCharge.calculatePerInstallmentChargeAmount(loan,
+ loanCharge = loanChargeService.calculatePerInstallmentChargeAmount(loan,
ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()), percentage);
}
@@ -314,7 +315,7 @@ public class LoanChargeAssembler {
}
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
- return new LoanCharge(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
+ return loanChargeService.create(loan, chargeDefinition, amountPercentageAppliedTo, amount, chargeTime, chargeCalculation, dueDate,
chargePaymentMode, null, loanCharge, externalId);
}
@@ -324,7 +325,7 @@ public class LoanChargeAssembler {
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
- return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
- numberOfRepayments, BigDecimal.ZERO, externalId);
+ return loanChargeService.create(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate,
+ chargePaymentMode, numberOfRepayments, BigDecimal.ZERO, externalId);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 48ab671b5..aa18fdc61 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -288,9 +288,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
.equals(loan.transactionProcessingStrategy())) {
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
// date, if yes trigger reprocess else no reprocessing
- LoanTransaction lastPaymentTransaction = loan.getLastTransactionForReprocessing();
- if (lastPaymentTransaction != null
- && DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransaction.getTransactionDate())) {
+ final Optional lastPaymentTransactionDate = loanTransactionRepository.findLastTransactionDateForReprocessing(loan);
+ if (lastPaymentTransactionDate.isPresent()
+ && DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransactionDate.get())) {
reprocessRequired = false;
}
}
@@ -475,7 +475,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final Map changes = new LinkedHashMap<>(3);
if (loan.getActiveCharges().contains(loanCharge)) {
final BigDecimal amount = loanChargeService.calculateAmountPercentageAppliedTo(loan, loanCharge);
- final Map loanChargeChanges = loanCharge.update(command, amount);
+ final Map loanChargeChanges = loanChargeService.update(command, amount, loanCharge);
changes.putAll(loanChargeChanges);
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java
index 9edd3b22c..82d73a91d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanDisbursementService.java
@@ -244,8 +244,8 @@ public class LoanDisbursementService {
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
- final LoanCharge loanCharge = new LoanCharge(loan, chargeDefinition, principal, null, null, null, expectedDisbursementDate,
- null, null, BigDecimal.ZERO, externalId);
+ final LoanCharge loanCharge = loanChargeService.create(loan, chargeDefinition, principal, null, null, null,
+ expectedDisbursementDate, null, null, BigDecimal.ZERO, externalId);
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
disbursementDetails);
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index 0a6a793f1..c7b4a45ea 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -179,6 +179,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
private final LoanForeclosureValidator loanForeclosureValidator;
private final LoanTransactionMapper loanTransactionMapper;
private final LoanTransactionProcessingService loadTransactionProcessingService;
+ private final LoanBalanceService loanBalanceService;
@Override
public LoanAccountData retrieveOne(final Long loanId) {
@@ -2088,7 +2089,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
- final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchLoanForeclosureDetail(transactionDate);
+ final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanBalanceService.fetchLoanForeclosureDetail(loan,
+ transactionDate);
BigDecimal unrecognizedIncomePortion = null;
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -2236,7 +2238,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return loanRepositoryWrapper.existsByLoanId(loanId);
}
- private LoanTransaction deriveDefaultInterestWaiverTransaction(Loan loan) {
+ private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
Money possibleInterestToWaive = totalInterestOutstanding.copy();
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
@@ -2260,4 +2262,5 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return LoanTransaction.waiver(loan.getOffice(), loan, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
possibleInterestToWaive.zero(), ExternalId.empty());
}
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java
index ed1eedc90..1da92d4a3 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanTransactionProcessingServiceImpl.java
@@ -61,6 +61,8 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
private final LoanTermVariationsMapper loanMapper;
private final InterestScheduleModelRepositoryWrapper modelRepository;
+ private final LoanTransactionService loanTransactionService;
+ private final LoanBalanceService loanBalanceService;
@Override
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
@@ -86,7 +88,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
- progressiveContext.getAlreadyProcessedTransactions().addAll(loan.retrieveListOfTransactionsForReprocessing());
+ progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
progressiveContext.setChargedOff(loan.isChargedOff());
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
@@ -158,7 +160,9 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
public Optional processPostDisbursementTransactions(Loan loan) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
loan.getTransactionProcessingStrategyCode());
- final List allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
+ final List allNonContraTransactionsPostDisbursement = loanTransactionService
+ .retrieveListOfTransactionsForReprocessing(loan);
+
final List copyTransactions = new ArrayList<>();
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
@@ -173,7 +177,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
loan.getActiveCharges());
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
return Optional.of(changedTransactionDetail);
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 51ce03452..dae94b210 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -287,6 +287,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
private final LoanMapper loanMapper;
private final LoanTransactionProcessingService loanTransactionProcessingService;
+ private final LoanBalanceService loanBalanceService;
+ private final LoanTransactionService loanTransactionService;
@Transactional
@Override
@@ -572,7 +574,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSED,
actualDisbursementDate1);
loanDisbursementService.handleDisbursementTransaction(loan, actualDisbursementDate1, paymentDetail1);
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
final Money interestApplied = Money.of(loan.getCurrency(), loan.getSummary().getTotalInterestCharged());
/*
@@ -591,11 +593,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
- final List allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
+ final List allNonContraTransactionsPostDisbursement = loanTransactionService
+ .retrieveListOfTransactionsForReprocessing(loan);
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
reprocessLoanTransactionsService.reprocessTransactions(loan);
}
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan);
@@ -972,8 +975,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
newInterestPaymentWaiverTransaction.updateLoan(loan);
- final boolean isTransactionChronologicallyLatest = loan
- .isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
+ final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
+ newInterestPaymentWaiverTransaction);
if (newInterestPaymentWaiverTransaction.isNotZero()) {
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
@@ -1412,7 +1415,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney;
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
- Money receivableInterest = loan.getReceivableInterest(transactionDate);
+ Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
interestComponent = receivableInterest;
unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
@@ -2024,119 +2027,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
- public void updateLoanRepaymentScheduleDates(final Loan loan, final LocalDate meetingStartDate, final String recuringRule,
- final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays,
- final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
- // first repayment's from date is same as disbursement date.
- LocalDate tmpFromDate = loan.getDisbursementDate();
- final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
- final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
- final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
-
- LocalDate newRepaymentDate;
- LocalDate latestRepaymentDate = null;
- List installments = loan.getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
- LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
-
- // FIXME: AA this won't update repayment dates before current date.
- if (DateUtils.isAfter(oldDueDate, meetingStartDate) && DateUtils.isDateInTheFuture(oldDueDate)) {
- newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
- loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
-
- final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
- loanRepaymentInterval, tmpFromDate);
-
- if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) {
- newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
- loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
- }
-
- if (isHolidayEnabled) {
- newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
- }
- if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
- latestRepaymentDate = newRepaymentDate;
- }
-
- loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
- // reset from date to get actual daysInPeriod
- loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
- tmpFromDate = newRepaymentDate;// update with new repayment date
- } else {
- tmpFromDate = oldDueDate;
- }
- }
- if (latestRepaymentDate != null) {
- loan.setExpectedMaturityDate(latestRepaymentDate);
- }
- }
-
- private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval,
- final LocalDate startDate) {
- LocalDate dueRepaymentPeriodDate = startDate;
- final int repaidEvery = 2 * loanRepaymentInterval;
- switch (periodFrequencyType) {
- case DAYS -> dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
- case WEEKS -> dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
- case MONTHS -> dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
- case YEARS -> dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
- case INVALID, WHOLE_TERM -> {
- }
- }
- return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from startDate
- }
-
- private void updateLoanRepaymentScheduleDates(final Loan loan, final String recurringRule, final boolean isHolidayEnabled,
- final List holidays, final WorkingDays workingDays, final LocalDate presentMeetingDate, final LocalDate newMeetingDate,
- final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
- // first repayment's from date is same as disbursement date.
- // meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date
-
- LocalDate tmpFromDate = loan.getDisbursementDate();
- final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
- final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
- final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
-
- LocalDate newRepaymentDate;
- boolean isFirstTime = true;
- LocalDate latestRepaymentDate = null;
- List installments = loan.getRepaymentScheduleInstallments();
- for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
- LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
- if (!DateUtils.isBefore(oldDueDate, presentMeetingDate)) {
- if (isFirstTime) {
- isFirstTime = false;
- newRepaymentDate = newMeetingDate;
- } else {
- // tmpFromDate.plusDays(1) is done to make sure
- // getNewRepaymentMeetingDate method returns next meeting
- // date and not the same as tmpFromDate
- newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recurringRule, tmpFromDate, tmpFromDate.plusDays(1),
- loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
- }
-
- if (isHolidayEnabled) {
- newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
- }
- if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
- latestRepaymentDate = newRepaymentDate;
- }
- loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
- // reset from date to get actual daysInPeriod
-
- loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
-
- tmpFromDate = newRepaymentDate;// update with new repayment date
- } else {
- tmpFromDate = oldDueDate;
- }
- }
- if (latestRepaymentDate != null) {
- loan.setExpectedMaturityDate(latestRepaymentDate);
- }
- }
-
private void removeLoanCycle(final Loan loan) {
final List loansToUpdate;
if (loan.isGroupLoan()) {
@@ -2924,7 +2814,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
}
- final List loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
+ final List loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanTransactions.add(chargeOffTransaction);
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
loan.addLoanTransaction(chargeOffTransaction);
@@ -3117,9 +3007,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.addLoanTransaction(chargebackTransaction);
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
+ final List transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(),
- loan.getDisbursementDate(), loan.retrieveListOfTransactionsForReprocessing(), loan.getCurrency(),
- loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
+ loan.getDisbursementDate(), transactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
+ loan.getActiveCharges());
} else {
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
@@ -3215,7 +3106,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.adjustNetDisbursalAmount(loan.getApprovedPrincipal());
actualChanges.put(ACTUAL_DISBURSEMENT_DATE, "");
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
return actualChanges;
@@ -3246,7 +3137,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private void reverseExistingTransactions(final Loan loan) {
@@ -3319,7 +3210,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
return Optional.of(loanTransaction);
}
@@ -3383,7 +3274,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
changes.put("externalId", externalId);
loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), closureDate, externalId);
- final boolean isLastTransaction = loan.isChronologicallyLatestTransaction(loanTransaction, loan.getLoanTransactions());
+ final boolean isLastTransaction = loanTransactionRepository.isChronologicallyLatest(loanTransaction.getTransactionDate(),
+ loan);
if (!isLastTransaction) {
final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
throw new InvalidLoanStateTransitionException("close.loan", "must.occur.on.or.after.latest.transaction.date",
@@ -3395,7 +3287,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
} else if (totalOutstanding.isGreaterThanZero()) {
final String errorMessage = "A loan with money outstanding cannot be closed";
throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage,
@@ -3403,8 +3295,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
- if (loan.isOverPaid()) {
- final Money totalLoanOverpayment = loan.calculateTotalOverpayment();
+ if (loanBalanceService.isOverPaid(loan)) {
+ final Money totalLoanOverpayment = loanBalanceService.calculateTotalOverpayment(loan);
if (totalLoanOverpayment.isGreaterThanZero() && loan.getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
// TODO - KW - technically should set somewhere that this loan
// has 'overpaid' amount
@@ -3474,7 +3366,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
final LoanDisbursementDetails disbursementDetail = loan.getDisbursementDetails(lastTransactionDate,
lastDisbursalTransaction.getAmount());
- loan.updateLoanToLastDisbursalState(disbursementDetail);
+ loanBalanceService.updateLoanToLastDisbursalState(loan, disbursementDetail);
loan.getLoanTermVariations()
.removeIf(loanTermVariations -> (loanTermVariations.getTermType().isDueDateVariation()
&& DateUtils.isAfter(loanTermVariations.fetchDateValue(), lastTransactionDate))
@@ -3579,4 +3471,118 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse;
}
+
+ private void updateLoanRepaymentScheduleDates(final Loan loan, final LocalDate meetingStartDate, final String recuringRule,
+ final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays,
+ final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
+ // first repayment's from date is same as disbursement date.
+ LocalDate tmpFromDate = loan.getDisbursementDate();
+ final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
+ final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
+ final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
+
+ LocalDate newRepaymentDate;
+ LocalDate latestRepaymentDate = null;
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
+ LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
+
+ // FIXME: AA this won't update repayment dates before current date.
+ if (DateUtils.isAfter(oldDueDate, meetingStartDate) && DateUtils.isDateInTheFuture(oldDueDate)) {
+ newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
+ loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
+
+ final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
+ loanRepaymentInterval, tmpFromDate);
+
+ if (DateUtils.isAfter(newRepaymentDate, maxDateLimitForNewRepayment)) {
+ newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, meetingStartDate, tmpFromDate,
+ loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
+ }
+
+ if (isHolidayEnabled) {
+ newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
+ }
+ if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
+ latestRepaymentDate = newRepaymentDate;
+ }
+
+ loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
+ // reset from date to get actual daysInPeriod
+ loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
+ tmpFromDate = newRepaymentDate;// update with new repayment date
+ } else {
+ tmpFromDate = oldDueDate;
+ }
+ }
+ if (latestRepaymentDate != null) {
+ loan.setExpectedMaturityDate(latestRepaymentDate);
+ }
+ }
+
+ private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval,
+ final LocalDate startDate) {
+ LocalDate dueRepaymentPeriodDate = startDate;
+ final int repaidEvery = 2 * loanRepaymentInterval;
+ switch (periodFrequencyType) {
+ case DAYS -> dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
+ case WEEKS -> dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
+ case MONTHS -> dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
+ case YEARS -> dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
+ case INVALID, WHOLE_TERM -> {
+ }
+ }
+ return dueRepaymentPeriodDate.minusDays(1);// get 2n-1 range date from startDate
+ }
+
+ private void updateLoanRepaymentScheduleDates(final Loan loan, final String recurringRule, final boolean isHolidayEnabled,
+ final List holidays, final WorkingDays workingDays, final LocalDate presentMeetingDate, final LocalDate newMeetingDate,
+ final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
+ // first repayment's from date is same as disbursement date.
+ // meetingStartDate is used as seedDate Capture the seedDate from user and use the seedDate as meetingStart date
+
+ LocalDate tmpFromDate = loan.getDisbursementDate();
+ final PeriodFrequencyType repaymentPeriodFrequencyType = loan.getLoanRepaymentScheduleDetail().getRepaymentPeriodFrequencyType();
+ final Integer loanRepaymentInterval = loan.getLoanRepaymentScheduleDetail().getRepayEvery();
+ final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
+
+ LocalDate newRepaymentDate;
+ boolean isFirstTime = true;
+ LocalDate latestRepaymentDate = null;
+ List installments = loan.getRepaymentScheduleInstallments();
+ for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
+ LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
+ if (!DateUtils.isBefore(oldDueDate, presentMeetingDate)) {
+ if (isFirstTime) {
+ isFirstTime = false;
+ newRepaymentDate = newMeetingDate;
+ } else {
+ // tmpFromDate.plusDays(1) is done to make sure
+ // getNewRepaymentMeetingDate method returns next meeting
+ // date and not the same as tmpFromDate
+ newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recurringRule, tmpFromDate, tmpFromDate.plusDays(1),
+ loanRepaymentInterval, frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
+ }
+
+ if (isHolidayEnabled) {
+ newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
+ }
+ if (DateUtils.isBefore(latestRepaymentDate, newRepaymentDate)) {
+ latestRepaymentDate = newRepaymentDate;
+ }
+ loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
+ // reset from date to get actual daysInPeriod
+
+ loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
+
+ tmpFromDate = newRepaymentDate;// update with new repayment date
+ } else {
+ tmpFromDate = oldDueDate;
+ }
+ }
+ if (latestRepaymentDate != null) {
+ loan.setExpectedMaturityDate(latestRepaymentDate);
+ }
+ }
+
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
index 2e9d4823c..42753862b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanSummaryDataProvider.java
@@ -53,6 +53,7 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
private final EMICalculator emiCalculator;
private final LoanRepositoryWrapper loanRepository;
+ private final LoanTransactionService loanTransactionService;
private final InterestScheduleModelRepositoryWrapper modelRepository;
@Override
@@ -80,8 +81,8 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
}
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
- List transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
- .filter(t -> !t.isAccrualActivity()).toList();
+ final List transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)
+ .stream().filter(t -> !t.isAccrualActivity()).toList();
Pair changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
index 6e9757e7e..1f26b8519 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java
@@ -22,6 +22,8 @@ import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
@@ -35,6 +37,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
@@ -49,15 +53,27 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
private final LoanTransactionProcessingService loanTransactionProcessingService;
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
+ private final LoanBalanceService loanBalanceService;
+ private final LoanTransactionRepository loanTransactionRepository;
@Override
public void reprocessTransactions(final Loan loan) {
- final List allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
+ final List allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan);
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
allNonContraTransactionsPostDisbursement);
handleChangedDetail(changedTransactionDetail);
}
+ private List retrieveListOfTransactionsForReprocessing(final Loan loan) {
+ return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
+ .sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
+ }
+
+ private Predicate loanTransactionForReprocessingPredicate() {
+ return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
+ || transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
+ }
+
@Override
public void reprocessParticularTransactions(final Loan loan, final List loanTransactions) {
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
@@ -66,8 +82,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
@Override
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
- final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
- loan.retrieveListOfTransactionsForReprocessing());
+ final List transactions = retrieveListOfTransactionsForReprocessing(loan);
+ final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
handleChangedDetail(changedTransactionDetail);
}
@@ -101,7 +117,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
return;
}
loan.getLoanCharges().remove(loanCharge);
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
}
private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final Loan loan, final LoanCharge loanCharge) {
@@ -147,7 +163,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
loan.getLoanTransactions().addAll(newTransactions);
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
handleChangedDetail(changedTransactionDetail);
}
@@ -185,7 +201,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
final List newTransactions = changedTransactionDetail.getTransactionChanges().stream()
.map(TransactionChangeData::getNewTransaction).toList();
loan.getLoanTransactions().addAll(newTransactions);
- loan.updateLoanSummaryDerivedFields();
+ loanBalanceService.updateLoanSummaryDerivedFields(loan);
return changedTransactionDetail;
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
index 1992eef92..0311d062b 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java
@@ -60,6 +60,7 @@ import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidat
import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
@@ -91,6 +92,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
private final LoanChargeValidator loanChargeValidator;
private final LoanJournalEntryPoster journalEntryPoster;
+ private final LoanBalanceService loanBalanceService;
@Override
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
@@ -112,7 +114,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
Money unrecognizedIncome = transactionAmountAsMoney.zero();
Money interestComponent = transactionAmountAsMoney;
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
- Money receivableInterest = loan.getReceivableInterest(transactionDate);
+ Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
interestComponent = receivableInterest;
unrecognizedIncome = transactionAmountAsMoney.minus(receivableInterest);
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
index 2901e0716..04dd3aecb 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountAutoStarter.java
@@ -33,6 +33,9 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.ProgressiveLoanInterestRefundServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
@@ -48,63 +51,77 @@ public class LoanAccountAutoStarter {
@Bean
@Conditional(CreocoreLoanRepaymentScheduleTransactionProcessorCondition.class)
public CreocoreLoanRepaymentScheduleTransactionProcessor creocoreLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.class)
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor earlyPaymentLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.class)
public FineractStyleLoanRepaymentScheduleTransactionProcessor fineractStyleLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.class)
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor heavensFamilyLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor interestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
+ loanBalanceService);
}
@Bean
@Conditional(PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor principalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
+ loanBalanceService);
}
@Bean
@Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class)
- public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
- return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
}
@Bean
@Conditional(DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorCondition.class)
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor duePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
+ loanBalanceService);
}
@Bean
@Conditional(DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorCondition.class)
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor duePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(
- ExternalIdFactory externalIdFactory) {
- return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
+ final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
+ return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
+ loanBalanceService);
}
@Bean
@@ -117,11 +134,13 @@ public class LoanAccountAutoStarter {
@Bean
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
- public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
- LoanRepositoryWrapper loanRepositoryWrapper,
- @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory,
- LoanScheduleComponent loanSchedule) {
+ public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator,
+ final LoanRepositoryWrapper loanRepositoryWrapper,
+ final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
+ final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
+ final LoanTransactionService loanTransactionService, final LoanChargeValidator loanChargeValidator,
+ final LoanBalanceService loanBalanceService) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
- externalIdFactory, loanSchedule);
+ externalIdFactory, loanSchedule, loanTransactionService, loanChargeValidator, loanBalanceService);
}
}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 2d4b51cf5..47f02d53e 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -118,6 +118,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService
import org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
@@ -143,6 +144,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanStatusChangePlatfor
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionRelationReadService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
@@ -268,11 +270,11 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanChargeAssembler.class)
- public LoanChargeAssembler loanChargeAssembler(
-
- FromJsonHelper fromApiJsonHelper, ChargeRepositoryWrapper chargeRepository, LoanChargeRepository loanChargeRepository,
- LoanProductRepository loanProductRepository, ExternalIdFactory externalIdFactory) {
- return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory);
+ public LoanChargeAssembler loanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
+ final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository,
+ final ExternalIdFactory externalIdFactory, final LoanChargeService loanChargeService) {
+ return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory,
+ loanChargeService);
}
@Bean
@@ -331,14 +333,14 @@ public class LoanAccountConfiguration {
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
- LoanTransactionProcessingService loanTransactionProcessingService) {
+ LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService) {
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService,
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
- loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService);
+ loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService);
}
@Bean
@@ -402,7 +404,8 @@ public class LoanAccountConfiguration {
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService,
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService,
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper,
- LoanTransactionProcessingService loanTransactionProcessingService) {
+ LoanTransactionProcessingService loanTransactionProcessingService, final LoanBalanceService loanBalanceService,
+ LoanTransactionService loanTransactionService) {
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
@@ -417,7 +420,7 @@ public class LoanAccountConfiguration {
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService,
loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper,
- loanTransactionProcessingService);
+ loanTransactionProcessingService, loanBalanceService, loanTransactionService);
}
@Bean
@@ -434,10 +437,12 @@ public class LoanAccountConfiguration {
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService,
LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator,
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
- LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
+ LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine,
+ LoanBalanceService loanBalanceService, LoanTransactionService loanTransactionService) {
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
- reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine);
+ reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
+ loanTransactionService);
}
@Bean
@@ -457,9 +462,10 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanChargeService.class)
- public LoanChargeService loanChargeService(LoanChargeValidator loanChargeValidator,
- LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
- return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine);
+ public LoanChargeService loanChargeService(final LoanChargeValidator loanChargeValidator,
+ final LoanTransactionProcessingService loanTransactionProcessingService,
+ final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanBalanceService loanBalanceService) {
+ return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService);
}
@Bean
diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
index 07a9a070f..7f19ed333 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanAccountDelinquencyRangeEventSerializerTest.java
@@ -87,12 +87,17 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
@@ -122,6 +127,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
@Mock
private AvroDateTimeMapper mapper;
+ private final LoanChargeService loanChargeService = new LoanChargeService(mock(LoanChargeValidator.class),
+ mock(LoanTransactionProcessingService.class), mock(LoanLifecycleStateMachine.class), mock(LoanBalanceService.class));
+
private MockedStatic moneyHelper = Mockito.mockStatic(MoneyHelper.class);
private static final String CUSTOM_DATA_PREFIX = "test_data_loan_delinquency_range_business_event";
@@ -275,7 +283,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
.thenReturn(installmentDelinquencyTags);
when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
- .map(c -> c.getLoanCharge()).collect(Collectors.toList()));
+ .map(LoanInstallmentCharge::getLoanCharge).collect(Collectors.toSet()));
// when
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
@@ -431,8 +439,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
}
private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) {
- LoanCharge loanCharge = new LoanCharge(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE, ChargeCalculationType.FLAT,
- LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100), ExternalId.generate());
+ LoanCharge loanCharge = loanChargeService.create(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE,
+ ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100),
+ ExternalId.generate());
ReflectionTestUtils.setField(loanCharge, "id", 1L);
return loanCharge;
}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
index 32ac828bf..ac92c81a8 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/DefaultLoanLifecycleStateMachineTest.java
@@ -33,6 +33,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -48,6 +49,9 @@ class DefaultLoanLifecycleStateMachineTest {
@Mock
private BusinessEventNotifierService businessEventNotifierService;
+ @Mock
+ private LoanBalanceService loanBalanceService;
+
private DefaultLoanLifecycleStateMachine underTest;
private MockedStatic moneyHelperStatic;
@@ -58,7 +62,7 @@ class DefaultLoanLifecycleStateMachineTest {
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
- underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService);
+ underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService, loanBalanceService);
}
@AfterEach
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
index 7cd31e6ab..fa7807957 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanBuilder.java
@@ -41,6 +41,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.Loa
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.rate.domain.Rate;
@@ -61,7 +63,7 @@ public class LoanBuilder {
private Staff loanOfficer;
private CodeValue loanPurpose;
private LoanRepaymentScheduleTransactionProcessor transactionProcessor = new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
- mock(ExternalIdFactory.class));
+ mock(ExternalIdFactory.class), mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
private LoanProductRelatedDetail loanRepaymentScheduleDetail;
private LoanStatus loanStatus = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
private LoanSubStatus loanSubStatus;
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
index e0f725be5..06c8e218e 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTest.java
@@ -265,8 +265,23 @@ public class LoanTest {
* @return the {@link LoanCharge}
*/
private LoanCharge buildLoanCharge() {
- return new LoanCharge(mock(Loan.class), mock(Charge.class), new BigDecimal(100), new BigDecimal(100),
- ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1,
- new BigDecimal(100), ExternalId.generate());
+ final LoanCharge loanCharge = new LoanCharge();
+
+ loanCharge.setLoan(mock(Loan.class));
+ loanCharge.setCharge(mock(Charge.class));
+ loanCharge.setAmount(new BigDecimal(100));
+ loanCharge.setAmountOutstanding(new BigDecimal(100));
+ loanCharge.setChargeTime(ChargeTimeType.TRANCHE_DISBURSEMENT.getValue());
+ loanCharge.setChargeCalculation(ChargeCalculationType.FLAT.getValue());
+ loanCharge.setDueDate(LocalDate.of(2022, 6, 27));
+ loanCharge.setChargePaymentMode(ChargePaymentMode.REGULAR.getValue());
+ loanCharge.setPercentage(null);
+ loanCharge.setAmountPercentageAppliedTo(null);
+ loanCharge.setAmountPaid(null);
+ loanCharge.setAmountWaived(null);
+ loanCharge.setAmountWrittenOff(null);
+ loanCharge.setExternalId(ExternalId.generate());
+
+ return loanCharge;
}
}
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java
index 474db7b09..00c3467f9 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorTest.java
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq;
+import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -34,6 +35,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -44,6 +46,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -99,7 +103,8 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
@BeforeEach
public void setUp() {
- underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(null);
+ underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
+ mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
Mockito.reset(charges, transactionMappings);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java
index 94f2d609e..0b0d212b7 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorTest.java
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.refEq;
+import static org.mockito.Mockito.mock;
import java.math.BigDecimal;
import java.math.MathContext;
@@ -34,6 +35,7 @@ import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
import org.apache.fineract.infrastructure.core.domain.ActionContext;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
@@ -44,6 +46,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
+import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
+import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
@@ -100,7 +104,8 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
@BeforeEach
public void setUp() {
- underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(null);
+ underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
+ mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
Mockito.reset(charges, transactionMappings);
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java
index 77c74d56c..7323ea61b 100644
--- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/service/LoanDownPaymentHandlerServiceImplTest.java
@@ -111,13 +111,20 @@ public class LoanDownPaymentHandlerServiceImplTest {
@Mock
private LoanLifecycleStateMachine loanLifecycleStateMachine;
+ @Mock
+ private LoanBalanceService loanBalanceService;
+
+ @Mock
+ private LoanTransactionService loanTransactionService;
+
private LoanDownPaymentHandlerServiceImpl underTest;
@BeforeEach
public void setUp() {
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
- reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine);
+ reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
+ loanTransactionService);
moneyHelper.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);