FINERACT-2080: Extract calculations from loan entity
This commit is contained in:
committed by
Adam Saghy
parent
18a55874ba
commit
47d5330802
+5
-2
@@ -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
|
||||
|
||||
+5
-4
@@ -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<LoanCharge> loanCharges = createMockCharges();
|
||||
final Set<LoanCharge> 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<LoanCharge> createMockCharges() {
|
||||
List<LoanCharge> loanCharges = new ArrayList<>();
|
||||
private Set<LoanCharge> createMockCharges() {
|
||||
final Set<LoanCharge> 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));
|
||||
|
||||
+65
-1
@@ -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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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<LoanStatus> 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);
|
||||
|
||||
|
||||
+15
-374
@@ -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<Long> {
|
||||
@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<Long> {
|
||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private List<LoanTermVariations> 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<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Long> {
|
||||
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<Long> fetchLoanTrancheChargeIds() {
|
||||
return getCharges().stream()//
|
||||
.filter(charge -> charge.isTrancheDisbursementCharge() && charge.isActive()) //
|
||||
@@ -793,7 +744,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private LocalDate determineExpectedMaturityDate() {
|
||||
public LocalDate determineExpectedMaturityDate() {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
|
||||
final int numberOfInstallments = installments.size();
|
||||
@@ -918,16 +869,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.postDatedChecks = new ArrayList<>();
|
||||
}
|
||||
|
||||
public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() {
|
||||
return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
|
||||
return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
|
||||
|| transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
|
||||
}
|
||||
|
||||
public List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
|
||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
@@ -945,95 +886,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.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<LoanTransaction> 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<LoanTransaction> 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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> 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<Long> {
|
||||
.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<LoanRepaymentScheduleInstallment> 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<Long> {
|
||||
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<Long> {
|
||||
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<Long> {
|
||||
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<LoanDisbursementDetails> getAllDisbursementDetails() {
|
||||
return this.disbursementDetails;
|
||||
}
|
||||
@@ -1800,21 +1595,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Long> {
|
||||
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<LoanRepaymentScheduleInstallment> installments) {
|
||||
this.repaymentScheduleInstallments.clear();
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
@@ -2046,7 +1687,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.loanTopupDetails;
|
||||
}
|
||||
|
||||
public Collection<LoanCharge> getLoanCharges() {
|
||||
public Set<LoanCharge> getLoanCharges() {
|
||||
return this.charges;
|
||||
}
|
||||
|
||||
|
||||
+3
-347
@@ -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<Long> {
|
||||
@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<Long> {
|
||||
@OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||
private Set<LoanChargePaidBy> 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<Long> {
|
||||
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<String, Object> update(final JsonCommand command, final BigDecimal amount) {
|
||||
|
||||
final Map<String, Object> 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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
amount = amount.plus(calculateInstallmentChargeAmount(loan, getChargeCalculation(), getPercentage(), installment));
|
||||
}
|
||||
return amount.getAmount();
|
||||
}
|
||||
|
||||
private List<LoanInstallmentCharge> generateInstallmentLoanCharges(final LoanCharge loanCharge) {
|
||||
Loan loan = this.loan;
|
||||
final List<LoanInstallmentCharge> loanChargePerInstallments = new ArrayList<>();
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
List<LoanRepaymentScheduleInstallment> 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<LoanInstallmentCharge> remove = new HashSet<>();
|
||||
final List<LoanInstallmentCharge> newChargeInstallments = generateInstallmentLoanCharges(this);
|
||||
if (this.loanInstallmentCharge.isEmpty()) {
|
||||
this.loanInstallmentCharge.addAll(newChargeInstallments);
|
||||
} else {
|
||||
int index = 0;
|
||||
final List<LoanInstallmentCharge> 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<Long> {
|
||||
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<Long> {
|
||||
* @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<Long> {
|
||||
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) {
|
||||
|
||||
+1
@@ -925,4 +925,5 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
public void updateTransactionDate(final LocalDate transactionDate) {
|
||||
this.dateOf = transactionDate;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+28
@@ -70,4 +70,32 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
||||
|
||||
@Query(FIND_LOAN_ID_BY_ID)
|
||||
Optional<Long> 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<LocalDate> findLastTransactionDateForReprocessing(@Param("loan") Loan loan);
|
||||
|
||||
}
|
||||
|
||||
+4
-2
@@ -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());
|
||||
}
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+5
-2
@@ -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
|
||||
|
||||
+6
-1
@@ -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<LoanCharge> 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<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||
|
||||
+9
-2
@@ -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;
|
||||
* </p>
|
||||
*/
|
||||
@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;
|
||||
|
||||
+9
-2
@@ -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;
|
||||
|
||||
+6
-1
@@ -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<ApiParameterError> 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",
|
||||
|
||||
+278
@@ -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<LoanRepaymentScheduleInstallment> 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;
|
||||
}
|
||||
|
||||
}
|
||||
+496
-147
@@ -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<LoanCharge> 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<Long> existingTransactionIds,
|
||||
final List<Long> 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<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
|
||||
List<LoanRepaymentScheduleInstallment> 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<LoanCharge> 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<LoanCharge> loanCharges) {
|
||||
List<Long> 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<Long> fetchAllLoanChargeIds(final Loan loan) {
|
||||
List<Long> 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<LoanCharge> loanCharges) {
|
||||
List<Long> 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<LoanRepaymentScheduleInstallment> 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<String, Object> update(final JsonCommand command, final BigDecimal amount, final LoanCharge loanCharge) {
|
||||
final Map<String, Object> 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<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
|
||||
List<LoanRepaymentScheduleInstallment> 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<LoanCharge> 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<LoanInstallmentCharge> remove = new HashSet<>();
|
||||
final List<LoanInstallmentCharge> newChargeInstallments = generateInstallmentLoanCharges(loanCharge.getLoan(), loanCharge);
|
||||
if (loanCharge.getLoanInstallmentCharge().isEmpty()) {
|
||||
loanCharge.getLoanInstallmentCharge().addAll(newChargeInstallments);
|
||||
} else {
|
||||
int index = 0;
|
||||
final List<LoanInstallmentCharge> 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<LoanInstallmentCharge> generateInstallmentLoanCharges(final Loan loan, final LoanCharge loanCharge) {
|
||||
final List<LoanInstallmentCharge> loanChargePerInstallments = new ArrayList<>();
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
List<LoanRepaymentScheduleInstallment> 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<Long> fetchAllLoanChargeIds(final Loan loan) {
|
||||
List<Long> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+6
-2
@@ -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()) {
|
||||
|
||||
+58
@@ -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<LoanTransaction> 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<LocalDate> lastTransactionDateForReprocessing = loanTransactionRepository
|
||||
.findLastTransactionDateForReprocessing(loan);
|
||||
|
||||
return lastTransactionDateForReprocessing.isEmpty()
|
||||
|| !DateUtils.isAfter(lastTransactionDateForReprocessing.get(), loanTransaction.getTransactionDate());
|
||||
}
|
||||
|
||||
private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
|
||||
return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
|
||||
|| transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
|
||||
}
|
||||
|
||||
}
|
||||
+5
-2
@@ -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<LoanScheduleModelPeriod> 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<LoanRepaymentScheduleInstallment> 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<LoanRepaymentScheduleInstallment> installments,
|
||||
|
||||
+9
-1
@@ -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<Money> penaltyChargesWaived = ArgumentCaptor.forClass(Money.class);
|
||||
private ArgumentCaptor<Money> 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);
|
||||
}
|
||||
|
||||
|
||||
+12
-5
@@ -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<LoanTransaction> transactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
|
||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||
Set<LoanCharge> charges = loan.getActiveCharges();
|
||||
@@ -1514,7 +1521,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
newTransaction.updateLoan(loan);
|
||||
loan.getLoanTransactions().add(newTransaction);
|
||||
}
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||
final LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
|
||||
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
||||
.reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
|
||||
|
||||
+9
-3
@@ -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<MoneyHelper> 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);
|
||||
|
||||
+8
-4
@@ -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<LoanRepaymentScheduleInstallment> 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())) {
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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))
|
||||
|
||||
+3
-2
@@ -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
|
||||
|
||||
+6
-5
@@ -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<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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<LocalDate> 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<String, Object> changes = new LinkedHashMap<>(3);
|
||||
if (loan.getActiveCharges().contains(loanCharge)) {
|
||||
final BigDecimal amount = loanChargeService.calculateAmountPercentageAppliedTo(loan, loanCharge);
|
||||
final Map<String, Object> loanChargeChanges = loanCharge.update(command, amount);
|
||||
final Map<String, Object> loanChargeChanges = loanChargeService.update(command, amount, loanCharge);
|
||||
changes.putAll(loanChargeChanges);
|
||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||
}
|
||||
|
||||
+2
-2
@@ -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);
|
||||
|
||||
+5
-2
@@ -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<PaymentTypeData> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-3
@@ -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<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||
loan.getTransactionProcessingStrategyCode());
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
|
||||
.retrieveListOfTransactionsForReprocessing(loan);
|
||||
|
||||
final List<LoanTransaction> 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);
|
||||
}
|
||||
|
||||
+136
-130
@@ -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<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> 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<Holiday> 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<LoanRepaymentScheduleInstallment> 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<Holiday> 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<LoanRepaymentScheduleInstallment> 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<Loan> 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<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> 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<LoanTransaction> 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<Holiday> 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<LoanRepaymentScheduleInstallment> 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<Holiday> 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<LoanRepaymentScheduleInstallment> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -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<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
|
||||
.filter(t -> !t.isAccrualActivity()).toList();
|
||||
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)
|
||||
.stream().filter(t -> !t.isAccrualActivity()).toList();
|
||||
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
|
||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||
|
||||
+22
-6
@@ -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<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan);
|
||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
||||
allNonContraTransactionsPostDisbursement);
|
||||
handleChangedDetail(changedTransactionDetail);
|
||||
}
|
||||
|
||||
private List<LoanTransaction> retrieveListOfTransactionsForReprocessing(final Loan loan) {
|
||||
return loan.getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate())
|
||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
|
||||
return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
|
||||
|| transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> 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<LoanTransaction> 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<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||
.map(TransactionChangeData::getNewTransaction).toList();
|
||||
loan.getLoanTransactions().addAll(newTransactions);
|
||||
loan.updateLoanSummaryDerivedFields();
|
||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+42
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -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
|
||||
|
||||
+12
-3
@@ -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> 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;
|
||||
}
|
||||
|
||||
+5
-1
@@ -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<MoneyHelper> 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
|
||||
|
||||
+3
-1
@@ -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;
|
||||
|
||||
+18
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -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));
|
||||
|
||||
+6
-1
@@ -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));
|
||||
|
||||
+8
-1
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user