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.infrastructure.core.service.ExternalIdFactory;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
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;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@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 static final String STRATEGY_NAME = "ACME Corp.: standard loan transaction processing strategy";
|
||||||
|
|
||||||
public AcmeLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public AcmeLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+5
-4
@@ -40,9 +40,10 @@ import static org.mockito.Mockito.when;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.apache.fineract.avro.generator.ByteBufferSerializable;
|
import org.apache.fineract.avro.generator.ByteBufferSerializable;
|
||||||
@@ -135,7 +136,7 @@ public class InvestorBusinessEventSerializerTest {
|
|||||||
.thenReturn(createTransferData(firstTransferStatus, null));
|
.thenReturn(createTransferData(firstTransferStatus, null));
|
||||||
Loan loan = Mockito.mock(Loan.class);
|
Loan loan = Mockito.mock(Loan.class);
|
||||||
when(loan.getCurrency()).thenReturn(new MonetaryCurrency("EUR", 2, 1));
|
when(loan.getCurrency()).thenReturn(new MonetaryCurrency("EUR", 2, 1));
|
||||||
List<LoanCharge> loanCharges = createMockCharges();
|
final Set<LoanCharge> loanCharges = createMockCharges();
|
||||||
when(loan.getLoanCharges()).thenReturn(loanCharges);
|
when(loan.getLoanCharges()).thenReturn(loanCharges);
|
||||||
LoanOwnershipTransferBusinessEvent loanOwnershipTransferBusinessEvent = new LoanOwnershipTransferBusinessEvent(
|
LoanOwnershipTransferBusinessEvent loanOwnershipTransferBusinessEvent = new LoanOwnershipTransferBusinessEvent(
|
||||||
createExternalAssetOwnerTransfer(status, subStatus), loan);
|
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));
|
assertEquals(CUSTOM_DATA_PREFIX + "_2", new String(customData.get("test_key_2").array(), UTF_8));
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanCharge> createMockCharges() {
|
private Set<LoanCharge> createMockCharges() {
|
||||||
List<LoanCharge> loanCharges = new ArrayList<>();
|
final Set<LoanCharge> loanCharges = new HashSet<>();
|
||||||
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("10.00000")));
|
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("10.00000")));
|
||||||
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("15.00000")));
|
loanCharges.add(loanCharge(1L, "charge a", new BigDecimal("15.00000")));
|
||||||
loanCharges.add(loanCharge(2L, "charge b", BigDecimal.ZERO));
|
loanCharges.add(loanCharge(2L, "charge b", BigDecimal.ZERO));
|
||||||
|
|||||||
+65
-1
@@ -18,6 +18,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.portfolio.delinquency.service;
|
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.time.LocalDate;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@@ -27,6 +30,7 @@ import java.util.stream.Collector;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
|
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.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
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.DelinquencyPausePeriod;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.InstallmentLevelDelinquency;
|
import org.apache.fineract.portfolio.loanaccount.data.InstallmentLevelDelinquency;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
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.LoanRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -142,7 +147,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
|||||||
// loans
|
// loans
|
||||||
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
collectionData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
|
||||||
collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()));
|
collectionData.setAvailableDisbursementAmount(loan.getApprovedPrincipal().subtract(loan.getDisbursedAmount()));
|
||||||
collectionData.setNextPaymentDueDate(loan.possibleNextRepaymentDate(nextPaymentDueDateConfig));
|
collectionData.setNextPaymentDueDate(possibleNextRepaymentDate(nextPaymentDueDateConfig, loan));
|
||||||
|
|
||||||
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
final LoanTransaction lastPayment = loan.getLastPaymentTransaction();
|
||||||
if (lastPayment != null) {
|
if (lastPayment != null) {
|
||||||
@@ -228,4 +233,63 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
|||||||
return List.of();
|
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.core.service.MathUtil;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanStatusChangedBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
// TODO: introduce tests for the state machine
|
// 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 static final List<LoanStatus> ALLOWED_LOAN_STATUSES = List.of(LoanStatus.values());
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
|
public LoanStatus dryTransition(final LoanEvent loanEvent, final Loan loan) {
|
||||||
@@ -45,7 +47,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void transition(final LoanEvent loanEvent, final Loan loan) {
|
public void transition(final LoanEvent loanEvent, final Loan loan) {
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
internalTransition(loanEvent, loan);
|
internalTransition(loanEvent, loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +57,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
|
||||||
final LoanStatusTransition transition = determineTransition(loan, loan.getStatus(), transactionDate);
|
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.organisation.staff.domain.Staff;
|
||||||
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
|
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
|
||||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
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.client.domain.Client;
|
||||||
import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
|
import org.apache.fineract.portfolio.collateral.domain.LoanCollateral;
|
||||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
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.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
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.LoanProduct;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
||||||
@@ -275,6 +273,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@Column(name = "interest_calculated_from_date")
|
@Column(name = "interest_calculated_from_date")
|
||||||
private LocalDate interestChargedFromDate;
|
private LocalDate interestChargedFromDate;
|
||||||
|
|
||||||
|
@Setter
|
||||||
@Column(name = "total_overpaid_derived", scale = 6, precision = 19)
|
@Column(name = "total_overpaid_derived", scale = 6, precision = 19)
|
||||||
private BigDecimal totalOverpaid;
|
private BigDecimal totalOverpaid;
|
||||||
|
|
||||||
@@ -347,6 +346,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
private List<LoanTermVariations> loanTermVariations = new ArrayList<>();
|
private List<LoanTermVariations> loanTermVariations = new ArrayList<>();
|
||||||
|
|
||||||
|
@Setter
|
||||||
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
|
@Column(name = "total_recovered_derived", scale = 6, precision = 19)
|
||||||
private BigDecimal totalRecovered;
|
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() {
|
private boolean isInterestRecalculationEnabledForProduct() {
|
||||||
return this.loanProduct.isInterestRecalculationEnabled();
|
return this.loanProduct.isInterestRecalculationEnabled();
|
||||||
}
|
}
|
||||||
@@ -746,33 +724,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.loanProduct.isMultiDisburseLoan();
|
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() {
|
public List<Long> fetchLoanTrancheChargeIds() {
|
||||||
return getCharges().stream()//
|
return getCharges().stream()//
|
||||||
.filter(charge -> charge.isTrancheDisbursementCharge() && charge.isActive()) //
|
.filter(charge -> charge.isTrancheDisbursementCharge() && charge.isActive()) //
|
||||||
@@ -793,7 +744,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate determineExpectedMaturityDate() {
|
public LocalDate determineExpectedMaturityDate() {
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
|
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
|
||||||
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
|
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
|
||||||
final int numberOfInstallments = installments.size();
|
final int numberOfInstallments = installments.size();
|
||||||
@@ -918,16 +869,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.postDatedChecks = new ArrayList<>();
|
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() {
|
public List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
|
||||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
@@ -945,95 +886,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
.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) {
|
public boolean isAfterLastRepayment(final LoanTransaction loanTransaction, final List<LoanTransaction> loanTransactions) {
|
||||||
return loanTransactions.stream() //
|
return loanTransactions.stream() //
|
||||||
.filter(t -> t.isRepaymentLikeType() && t.isNotReversed()) //
|
.filter(t -> t.isRepaymentLikeType() && t.isNotReversed()) //
|
||||||
.noneMatch(t -> DateUtils.isBefore(loanTransaction.getTransactionDate(), t.getTransactionDate()));
|
.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() {
|
public LoanTransaction findWriteOffTransaction() {
|
||||||
return this.loanTransactions.stream() //
|
return this.loanTransactions.stream() //
|
||||||
.filter(transaction -> !transaction.isReversed() && transaction.isWriteOff()) //
|
.filter(transaction -> !transaction.isReversed() && transaction.isWriteOff()) //
|
||||||
@@ -1041,49 +899,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
.orElse(null);
|
.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() {
|
public Money calculateTotalRecoveredPayments() {
|
||||||
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
|
// in case logic for reversing recovered payment is implemented handle subtraction from totalRecoveredPayments
|
||||||
return getTotalRecoveredPayments();
|
return getTotalRecoveredPayments();
|
||||||
@@ -1392,30 +1207,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return isChargedOff() && getChargeOffReason() != null ? getChargeOffReason().getId() : null;
|
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() {
|
public boolean isSyncDisbursementWithMeeting() {
|
||||||
return this.syncDisbursementWithMeeting != null && this.syncDisbursementWithMeeting;
|
return this.syncDisbursementWithMeeting != null && this.syncDisbursementWithMeeting;
|
||||||
}
|
}
|
||||||
@@ -1480,13 +1271,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return currentTransactionDate;
|
return currentTransactionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanTransaction getLastTransactionForReprocessing() {
|
|
||||||
return loanTransactions.stream() //
|
|
||||||
.filter(Loan.loanTransactionForReprocessingPredicate()) //
|
|
||||||
.reduce((first, second) -> second) //
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanTransaction getLastPaymentTransaction() {
|
public LoanTransaction getLastPaymentTransaction() {
|
||||||
return loanTransactions.stream() //
|
return loanTransactions.stream() //
|
||||||
.filter(loanTransaction -> !loanTransaction.isReversed()) //
|
.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());
|
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() {
|
public List<LoanDisbursementDetails> getAllDisbursementDetails() {
|
||||||
return this.disbursementDetails;
|
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() {
|
private int adjustNumberOfRepayments() {
|
||||||
int repaymetsForAdjust = 0;
|
int repaymetsForAdjust = 0;
|
||||||
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
|
for (LoanTermVariations loanTermVariations : this.loanTermVariations) {
|
||||||
@@ -1867,145 +1647,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.writeOffReason = writeOffReason;
|
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) {
|
public void updateLoanScheduleOnForeclosure(final Collection<LoanRepaymentScheduleInstallment> installments) {
|
||||||
this.repaymentScheduleInstallments.clear();
|
this.repaymentScheduleInstallments.clear();
|
||||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
@@ -2046,7 +1687,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return this.loanTopupDetails;
|
return this.loanTopupDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<LoanCharge> getLoanCharges() {
|
public Set<LoanCharge> getLoanCharges() {
|
||||||
return this.charges;
|
return this.charges;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-347
@@ -33,18 +33,13 @@ import java.math.BigDecimal;
|
|||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
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.data.EnumOptionData;
|
||||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
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.ChargeCalculationType;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
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.LoanChargeData;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||||
|
|
||||||
|
@Setter
|
||||||
@Getter
|
@Getter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
|
@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)
|
@Column(name = "amount_paid_derived", scale = 6, precision = 19)
|
||||||
private BigDecimal amountPaid;
|
private BigDecimal amountPaid;
|
||||||
|
|
||||||
@Setter
|
|
||||||
@Column(name = "amount_waived_derived", scale = 6, precision = 19)
|
@Column(name = "amount_waived_derived", scale = 6, precision = 19)
|
||||||
private BigDecimal amountWaived;
|
private BigDecimal amountWaived;
|
||||||
|
|
||||||
@@ -149,111 +143,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
@OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
@OneToMany(mappedBy = "loanCharge", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
|
||||||
private Set<LoanChargePaidBy> loanChargePaidBySet = new HashSet<>();
|
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() {
|
public void markAsFullyPaid() {
|
||||||
this.amountPaid = this.amount;
|
this.amountPaid = this.amount;
|
||||||
this.amountOutstanding = BigDecimal.ZERO;
|
this.amountOutstanding = BigDecimal.ZERO;
|
||||||
@@ -340,234 +229,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
this.loan = loan;
|
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() {
|
public boolean isDueAtDisbursement() {
|
||||||
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT)
|
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT)
|
||||||
|| ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.TRANCHE_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;
|
return BigDecimal.ZERO.compareTo(calculateOutstanding()) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal calculateOutstanding() {
|
public BigDecimal calculateOutstanding() {
|
||||||
if (this.amount == null) {
|
if (this.amount == null) {
|
||||||
return 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
|
* @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
|
* amount is within min and max cap
|
||||||
*/
|
*/
|
||||||
private BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
|
public BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
|
||||||
BigDecimal minMaxCap;
|
BigDecimal minMaxCap;
|
||||||
if (this.minCap != null) {
|
if (this.minCap != null) {
|
||||||
final int minimumCap = percentageOf.compareTo(this.minCap);
|
final int minimumCap = percentageOf.compareTo(this.minCap);
|
||||||
@@ -817,11 +478,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return ChargeCalculationType.fromInt(this.chargeCalculation);
|
return ChargeCalculationType.fromInt(this.chargeCalculation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateAmount(final BigDecimal amount) {
|
|
||||||
this.amount = amount;
|
|
||||||
calculateOutstanding();
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
|
public LoanInstallmentCharge getUnpaidInstallmentLoanCharge() {
|
||||||
LoanInstallmentCharge unpaidChargePerInstallment = null;
|
LoanInstallmentCharge unpaidChargePerInstallment = null;
|
||||||
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
|
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
|
||||||
|
|||||||
+1
@@ -925,4 +925,5 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
public void updateTransactionDate(final LocalDate transactionDate) {
|
public void updateTransactionDate(final LocalDate transactionDate) {
|
||||||
this.dateOf = transactionDate;
|
this.dateOf = transactionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+28
@@ -70,4 +70,32 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
|
|||||||
|
|
||||||
@Query(FIND_LOAN_ID_BY_ID)
|
@Query(FIND_LOAN_ID_BY_ID)
|
||||||
Optional<Long> findLoanIdById(@Param("id") Long 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.HeavensFamilyLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,8 +77,9 @@ import org.springframework.util.CollectionUtils;
|
|||||||
public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
|
public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implements LoanRepaymentScheduleTransactionProcessor {
|
||||||
|
|
||||||
protected final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
|
protected final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
|
||||||
protected final LoanChargeValidator loanChargeValidator = new LoanChargeValidator();
|
|
||||||
protected final ExternalIdFactory externalIdFactory;
|
protected final ExternalIdFactory externalIdFactory;
|
||||||
|
protected final LoanChargeValidator loanChargeValidator;
|
||||||
|
protected final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean accept(String s) {
|
public boolean accept(String s) {
|
||||||
@@ -256,7 +258,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
} else {
|
} else {
|
||||||
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||||
final Loan loan = loanTransaction.getLoan();
|
final Loan loan = loanTransaction.getLoan();
|
||||||
if ((loan.isClosedObligationsMet() || loan.isOverPaid()) && currentInstallment.isObligationsMet()
|
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
|
||||||
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
|
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
|
||||||
loanTransaction.updateTransactionDate(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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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}.
|
* Creocore style {@link LoanRepaymentScheduleTransactionProcessor}.
|
||||||
@@ -47,8 +49,9 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
|
|||||||
|
|
||||||
public static final String STRATEGY_NAME = "Creocore Unique";
|
public static final String STRATEGY_NAME = "Creocore Unique";
|
||||||
|
|
||||||
public CreocoreLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public CreocoreLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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
|
* `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 static final String STRATEGY_NAME = "Due penalty, fee, interest, principal, In advance principal, penalty, fee, interest";
|
||||||
|
|
||||||
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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
|
* `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 static final String STRATEGY_NAME = "Due penalty, interest, principal, fee, In advance penalty, interest, principal, fee";
|
||||||
|
|
||||||
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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
|
* 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 static final String STRATEGY_NAME = "Early Repayment Strategy";
|
||||||
|
|
||||||
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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}.
|
* 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 static final String STRATEGY_NAME = "Penalties, Fees, Interest, Principal order";
|
||||||
|
|
||||||
public FineractStyleLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public FineractStyleLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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}.
|
* Heavensfamily style {@link LoanRepaymentScheduleTransactionProcessor}.
|
||||||
@@ -49,8 +51,9 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
|
|||||||
|
|
||||||
public static final String STRATEGY_NAME = "HeavensFamily Unique";
|
public static final String STRATEGY_NAME = "HeavensFamily Unique";
|
||||||
|
|
||||||
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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
|
* 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 static final String STRATEGY_NAME = "Interest, Principal, Penalties, Fees Order";
|
||||||
|
|
||||||
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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
|
* 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 static final String STRATEGY_NAME = "Principal, Interest, Penalties, Fees Order";
|
||||||
|
|
||||||
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.LoanTransactionToRepaymentScheduleMapping;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
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}.
|
* 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 static final String STRATEGY_NAME = "Overdue/Due Fee/Int,Principal";
|
||||||
|
|
||||||
public RBILoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public RBILoanRepaymentScheduleTransactionProcessor(final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+6
-1
@@ -35,6 +35,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
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.MultiDisbursementEmiAmountException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.ScheduleDateException;
|
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;
|
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
|
public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||||
|
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||||
@@ -2796,7 +2801,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
|||||||
|
|
||||||
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
|
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
|
||||||
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
||||||
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
|
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||||
|
|||||||
+9
-2
@@ -25,10 +25,10 @@ import java.util.Collection;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
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.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@@ -56,12 +56,19 @@ import org.springframework.stereotype.Component;
|
|||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
public class CumulativeDecliningBalanceInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
||||||
|
|
||||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||||
|
|
||||||
|
public CumulativeDecliningBalanceInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
|
||||||
|
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
|
||||||
|
final LoanTransactionService loanTransactionService) {
|
||||||
|
super(loanTransactionService);
|
||||||
|
this.scheduledDateGenerator = scheduledDateGenerator;
|
||||||
|
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||||
return scheduledDateGenerator;
|
return scheduledDateGenerator;
|
||||||
|
|||||||
+9
-2
@@ -24,18 +24,25 @@ import java.time.LocalDate;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
public class CumulativeFlatInterestLoanScheduleGenerator extends AbstractCumulativeLoanScheduleGenerator {
|
||||||
|
|
||||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||||
|
|
||||||
|
public CumulativeFlatInterestLoanScheduleGenerator(final ScheduledDateGenerator scheduledDateGenerator,
|
||||||
|
final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator,
|
||||||
|
final LoanTransactionService loanTransactionService) {
|
||||||
|
super(loanTransactionService);
|
||||||
|
this.scheduledDateGenerator = scheduledDateGenerator;
|
||||||
|
this.paymentPeriodsInOneYearCalculator = paymentPeriodsInOneYearCalculator;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||||
return scheduledDateGenerator;
|
return scheduledDateGenerator;
|
||||||
|
|||||||
+6
-1
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.serialization;
|
|||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.organisation.holiday.domain.Holiday;
|
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.LoanEvent;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
public final class LoanDownPaymentTransactionValidator {
|
public final class LoanDownPaymentTransactionValidator {
|
||||||
|
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
public void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays,
|
||||||
final boolean allowTransactionsOnNonWorkingDay) {
|
final boolean allowTransactionsOnNonWorkingDay) {
|
||||||
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)) {
|
||||||
@@ -53,7 +58,7 @@ public final class LoanDownPaymentTransactionValidator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void validateLoanStatusIsActiveOrFullyPaidOrOverpaid(final Loan loan) {
|
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 List<ApiParameterError> dataValidationErrors = new ArrayList<>();
|
||||||
final String defaultUserMessage = "Loan must be Active, Fully Paid or Overpaid";
|
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",
|
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.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
|
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.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
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.LoanLifecycleStateMachine;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
@@ -52,6 +62,7 @@ public class LoanChargeService {
|
|||||||
private final LoanChargeValidator loanChargeValidator;
|
private final LoanChargeValidator loanChargeValidator;
|
||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
public void recalculateAllCharges(final Loan loan) {
|
public void recalculateAllCharges(final Loan loan) {
|
||||||
Set<LoanCharge> charges = loan.getActiveCharges();
|
Set<LoanCharge> charges = loan.getActiveCharges();
|
||||||
@@ -68,54 +79,25 @@ public class LoanChargeService {
|
|||||||
BigDecimal totalChargeAmt = BigDecimal.ZERO;
|
BigDecimal totalChargeAmt = BigDecimal.ZERO;
|
||||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||||
if (loanCharge.isOverdueInstallmentCharge()) {
|
if (loanCharge.isOverdueInstallmentCharge()) {
|
||||||
amount = loan.calculateOverdueAmountPercentageAppliedTo(loanCharge, penaltyWaitPeriod);
|
amount = calculateOverdueAmountPercentageAppliedTo(loan, loanCharge, penaltyWaitPeriod);
|
||||||
} else {
|
} else {
|
||||||
amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
|
amount = calculateAmountPercentageAppliedTo(loan, loanCharge);
|
||||||
}
|
}
|
||||||
chargeAmt = loanCharge.getPercentage();
|
chargeAmt = loanCharge.getPercentage();
|
||||||
if (loanCharge.isInstalmentFee()) {
|
if (loanCharge.isInstalmentFee()) {
|
||||||
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
|
totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chargeAmt = loanCharge.amountOrPercentage();
|
chargeAmt = loanCharge.amountOrPercentage();
|
||||||
}
|
}
|
||||||
if (loanCharge.isActive()) {
|
if (loanCharge.isActive()) {
|
||||||
clearLoanInstallmentChargesBeforeRegeneration(loanCharge);
|
clearLoanInstallmentChargesBeforeRegeneration(loanCharge);
|
||||||
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
|
update(loanCharge, chargeAmt, loanCharge.getDueLocalDate(), amount, loan.fetchNumberOfInstallmensAfterExceptions(),
|
||||||
totalChargeAmt);
|
totalChargeAmt);
|
||||||
loanChargeValidator.validateChargeHasValidSpecifiedDateIfApplicable(loan, loanCharge, loan.getDisbursementDate());
|
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,
|
public void makeChargePayment(final Loan loan, final Long chargeId, final List<Long> existingTransactionIds,
|
||||||
final List<Long> existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
|
final List<Long> existingReversedTransactionIds, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
|
||||||
loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction);
|
loanChargeValidator.validateChargePaymentNotInFuture(paymentTransaction);
|
||||||
@@ -130,36 +112,6 @@ public class LoanChargeService {
|
|||||||
handleChargePaidTransaction(loan, charge, paymentTransaction, installmentNumber);
|
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
|
* 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.
|
* 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()) {
|
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||||
chargeAmt = loanCharge.getPercentage();
|
chargeAmt = loanCharge.getPercentage();
|
||||||
if (loanCharge.isInstalmentFee()) {
|
if (loanCharge.isInstalmentFee()) {
|
||||||
totalChargeAmt = calculatePerInstallmentChargeAmount(loanCharge);
|
totalChargeAmt = calculatePerInstallmentChargeAmount(loan, loanCharge);
|
||||||
} else if (loanCharge.isOverdueInstallmentCharge()) {
|
} else if (loanCharge.isOverdueInstallmentCharge()) {
|
||||||
totalChargeAmt = loanCharge.amountOutstanding();
|
totalChargeAmt = loanCharge.amountOutstanding();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
chargeAmt = loanCharge.amountOrPercentage();
|
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
|
// NOTE: must add new loan charge to set of loan charges before
|
||||||
// reprocessing the repayment schedule.
|
// reprocessing the repayment schedule.
|
||||||
@@ -250,72 +202,11 @@ public class LoanChargeService {
|
|||||||
// store Id's of existing loan transactions and existing reversed loan transactions
|
// store Id's of existing loan transactions and existing reversed loan transactions
|
||||||
final SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
|
final SingleLoanChargeRepaymentScheduleProcessingWrapper wrapper = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
|
||||||
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loanCharge);
|
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loanCharge);
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
|
||||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_CHARGE_ADDED, 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) {
|
public BigDecimal calculateAmountPercentageAppliedTo(final Loan loan, final LoanCharge loanCharge) {
|
||||||
if (loanCharge.isOverdueInstallmentCharge()) {
|
if (loanCharge.isOverdueInstallmentCharge()) {
|
||||||
return loanCharge.getAmountPercentageAppliedTo();
|
return loanCharge.getAmountPercentageAppliedTo();
|
||||||
@@ -343,38 +234,254 @@ public class LoanChargeService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal getDerivedAmountForCharge(final Loan loan, final LoanCharge loanCharge) {
|
public void updateLoanCharges(final Loan loan, final Set<LoanCharge> loanCharges) {
|
||||||
BigDecimal amount = BigDecimal.ZERO;
|
List<Long> existingCharges = fetchAllLoanChargeIds(loan);
|
||||||
if (loan.isMultiDisburmentLoan() && loanCharge.getCharge().getChargeTimeType().equals(ChargeTimeType.DISBURSEMENT.getValue())) {
|
|
||||||
amount = loan.getApprovedPrincipal();
|
/* Process new and updated charges **/
|
||||||
} else {
|
for (final LoanCharge loanCharge : loanCharges) {
|
||||||
// If charge type is specified due date and loan is multi disburment loan.
|
LoanCharge charge = loanCharge;
|
||||||
// Then we need to get as of this loan charge due date how much amount disbursed.
|
// add new charges
|
||||||
if (loanCharge.isSpecifiedDueDate() && loan.isMultiDisburmentLoan()) {
|
if (loanCharge.getId() == null) {
|
||||||
for (final LoanDisbursementDetails loanDisbursementDetails : loan.getDisbursementDetails()) {
|
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge;
|
||||||
if (!DateUtils.isAfter(loanDisbursementDetails.expectedDisbursementDate(), loanCharge.getDueDate())) {
|
loanCharge.update(loan);
|
||||||
amount = amount.add(loanDisbursementDetails.principal());
|
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 {
|
} 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());
|
Money amount = Money.zero(loan.getCurrency());
|
||||||
if (loan.isMultiDisburmentLoan()) {
|
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||||
for (final LoanDisbursementDetails loanDisbursementDetail : loan.getDisbursementDetails()) {
|
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
amount = amount.plus(loanDisbursementDetail.principal());
|
amount = amount.plus(calculateInstallmentChargeAmount(loan, calculationType, percentage, installment));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return amount;
|
return amount.getAmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
private BigDecimal calculatePerInstallmentChargeAmount(final LoanCharge loanCharge) {
|
public Map<String, Object> update(final JsonCommand command, final BigDecimal amount, final LoanCharge loanCharge) {
|
||||||
return loanCharge.calculatePerInstallmentChargeAmount();
|
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) {
|
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 ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command,
|
public LoanTransaction handleDownPayment(ScheduleGeneratorDTO scheduleGeneratorDTO, JsonCommand command,
|
||||||
@@ -91,7 +93,8 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
|||||||
|
|
||||||
loanTransaction.updateLoan(loan);
|
loanTransaction.updateLoan(loan);
|
||||||
|
|
||||||
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
|
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
|
||||||
|
loanTransaction);
|
||||||
|
|
||||||
if (loanTransaction.isNotZero()) {
|
if (loanTransaction.isNotZero()) {
|
||||||
loan.addLoanTransaction(loanTransaction);
|
loan.addLoanTransaction(loanTransaction);
|
||||||
@@ -128,6 +131,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
|||||||
.fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate());
|
.fetchLoanRepaymentScheduleInstallmentByDueDate(loanTransaction.getTransactionDate());
|
||||||
|
|
||||||
boolean reprocessOnPostConditions = false;
|
boolean reprocessOnPostConditions = false;
|
||||||
|
|
||||||
boolean processLatest = isTransactionChronologicallyLatest //
|
boolean processLatest = isTransactionChronologicallyLatest //
|
||||||
&& adjustedTransaction == null // covers reversals
|
&& adjustedTransaction == null // covers reversals
|
||||||
&& !loan.isForeclosure() //
|
&& !loan.isForeclosure() //
|
||||||
@@ -163,7 +167,7 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|
|||||||
if (loanTransaction.isNotRecoveryRepayment()) {
|
if (loanTransaction.isNotRecoveryRepayment()) {
|
||||||
loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate());
|
loanLifecycleStateMachine.determineAndTransition(loan, loanTransaction.getTransactionDate());
|
||||||
} else {
|
} else {
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loan.getLoanProduct().isMultiDisburseLoan()) {
|
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.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class LoanScheduleComponent {
|
public class LoanScheduleComponent {
|
||||||
|
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
public void updateLoanSchedule(Loan loan, final LoanScheduleModel modifiedLoanSchedule) {
|
public void updateLoanSchedule(Loan loan, final LoanScheduleModel modifiedLoanSchedule) {
|
||||||
final List<LoanScheduleModelPeriod> periods = modifiedLoanSchedule.getPeriods();
|
final List<LoanScheduleModelPeriod> periods = modifiedLoanSchedule.getPeriods();
|
||||||
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
|
||||||
@@ -55,7 +58,7 @@ public class LoanScheduleComponent {
|
|||||||
loan.getRepaymentScheduleInstallments().removeIf(i -> !existInstallment(periods, i.getInstallmentNumber()));
|
loan.getRepaymentScheduleInstallments().removeIf(i -> !existInstallment(periods, i.getInstallmentNumber()));
|
||||||
|
|
||||||
loan.updateLoanScheduleDependentDerivedFields();
|
loan.updateLoanScheduleDependentDerivedFields();
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateLoanSchedule(Loan loan, final List<LoanRepaymentScheduleInstallment> installments) {
|
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.getRepaymentScheduleInstallments().removeIf(i -> !existInstallment(installments, i.getInstallmentNumber()));
|
||||||
|
|
||||||
loan.updateLoanScheduleDependentDerivedFields();
|
loan.updateLoanScheduleDependentDerivedFields();
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanRepaymentScheduleInstallment findByInstallmentNumber(final Collection<LoanRepaymentScheduleInstallment> installments,
|
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.ChargeCalculationType;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
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.jetbrains.annotations.NotNull;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@@ -64,6 +68,9 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
|
|||||||
private ArgumentCaptor<Money> penaltyChargesWaived = ArgumentCaptor.forClass(Money.class);
|
private ArgumentCaptor<Money> penaltyChargesWaived = ArgumentCaptor.forClass(Money.class);
|
||||||
private ArgumentCaptor<Money> penaltyChargesWrittenOff = 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
|
@BeforeAll
|
||||||
public static void init() {
|
public static void init() {
|
||||||
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
MONEY_HELPER.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.HALF_EVEN);
|
||||||
@@ -141,7 +148,8 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapperTest {
|
|||||||
when(charge.isPenalty()).thenReturn(penalty);
|
when(charge.isPenalty()).thenReturn(penalty);
|
||||||
Loan loan = mock(Loan.class);
|
Loan loan = mock(Loan.class);
|
||||||
when(loan.isInterestBearing()).thenReturn(false);
|
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);
|
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.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
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.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.loanaccount.service.schedule.LoanScheduleComponent;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
|
import org.apache.fineract.portfolio.loanproduct.calc.data.PeriodDueDetails;
|
||||||
@@ -118,13 +121,17 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||||
private final InterestRefundService interestRefundService;
|
private final InterestRefundService interestRefundService;
|
||||||
private final LoanScheduleComponent loanSchedule;
|
private final LoanScheduleComponent loanSchedule;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
public AdvancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator, LoanRepositoryWrapper loanRepositoryWrapper,
|
public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper,
|
||||||
InterestRefundService interestRefundService, ExternalIdFactory externalIdFactory, LoanScheduleComponent loanSchedule) {
|
final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory,
|
||||||
super(externalIdFactory);
|
final LoanScheduleComponent loanSchedule, final LoanTransactionService loanTransactionRepository,
|
||||||
|
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService) {
|
||||||
|
super(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
this.emiCalculator = emiCalculator;
|
this.emiCalculator = emiCalculator;
|
||||||
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
||||||
this.interestRefundService = interestRefundService;
|
this.interestRefundService = interestRefundService;
|
||||||
|
this.loanTransactionService = loanTransactionRepository;
|
||||||
this.loanSchedule = loanSchedule;
|
this.loanSchedule = loanSchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +276,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
|
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
|
||||||
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
|
public ProgressiveLoanInterestScheduleModel calculateInterestScheduleModel(@NotNull Long loanId, LocalDate targetDate) {
|
||||||
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
|
Loan loan = loanRepositoryWrapper.findOneWithNotFoundDetection(loanId);
|
||||||
List<LoanTransaction> transactions = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
|
MonetaryCurrency currency = loan.getLoanRepaymentScheduleDetail().getCurrency();
|
||||||
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
|
||||||
Set<LoanCharge> charges = loan.getActiveCharges();
|
Set<LoanCharge> charges = loan.getActiveCharges();
|
||||||
@@ -1514,7 +1521,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
newTransaction.updateLoan(loan);
|
newTransaction.updateLoan(loan);
|
||||||
loan.getLoanTransactions().add(newTransaction);
|
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 AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
|
||||||
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
|
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
|
||||||
private final InterestScheduleModelRepositoryWrapper writePlatformService;
|
private final InterestScheduleModelRepositoryWrapper writePlatformService;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
|
@SuppressFBWarnings("SLF4J_SIGN_ONLY_FORMAT")
|
||||||
@@ -94,7 +95,7 @@ public class InternalProgressiveLoanApiResource implements InitializingBean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ProgressiveLoanInterestScheduleModel reprocessTransactionsAndGetModel(final Loan loan) {
|
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 LocalDate businessDate = ThreadLocalContextUtil.getBusinessDate();
|
||||||
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
final Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
||||||
.reprocessProgressiveLoanTransactionsTransactional(loan.getDisbursementDate(), businessDate, transactionsToReprocess,
|
.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.businessdate.domain.BusinessDateType;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
import org.apache.fineract.infrastructure.core.domain.ActionContext;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
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.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
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.loanaccount.service.schedule.LoanScheduleComponent;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
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 static final MockedStatic<MoneyHelper> MONEY_HELPER = mockStatic(MoneyHelper.class);
|
||||||
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
private AdvancedPaymentScheduleTransactionProcessor underTest;
|
||||||
private static final EMICalculator emiCalculator = Mockito.mock(EMICalculator.class);
|
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
|
@BeforeAll
|
||||||
public static void init() {
|
public static void init() {
|
||||||
@@ -114,7 +117,10 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
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.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
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.LoanAccrualTransactionBusinessEventService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
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.LoanChargeService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanRefundService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanScheduleService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
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.LoanUtilService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanSupportedInterestRefundTypes;
|
||||||
@@ -147,7 +149,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
private final DelinquencyEffectivePauseHelper delinquencyEffectivePauseHelper;
|
||||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
|
||||||
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
private final InterestRefundServiceDelegate interestRefundServiceDelegate;
|
||||||
private final LoanTransactionValidator loanTransactionValidator;
|
private final LoanTransactionValidator loanTransactionValidator;
|
||||||
private final LoanForeclosureValidator loanForeclosureValidator;
|
private final LoanForeclosureValidator loanForeclosureValidator;
|
||||||
@@ -161,6 +162,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
|
||||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -767,7 +770,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
existingTransactionIds.addAll(loan.findExistingTransactionIds());
|
||||||
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
|
||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = null;
|
||||||
final LoanRepaymentScheduleInstallment foreCloseDetail = loan.fetchLoanForeclosureDetail(foreClosureDate);
|
final LoanRepaymentScheduleInstallment foreCloseDetail = loanBalanceService.fetchLoanForeclosureDetail(loan, foreClosureDate);
|
||||||
|
|
||||||
loanAccrualsProcessingService.processAccrualsOnLoanForeClosure(loan, foreClosureDate, newTransactions);
|
loanAccrualsProcessingService.processAccrualsOnLoanForeClosure(loan, foreClosureDate, newTransactions);
|
||||||
|
|
||||||
@@ -875,7 +878,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
LoanTransaction refundTransaction = LoanTransaction.refund(loan, loanTransactionType, transactionAmount, paymentDetail,
|
LoanTransaction refundTransaction = LoanTransaction.refund(loan, loanTransactionType, transactionAmount, paymentDetail,
|
||||||
transactionDate, txnExternalId);
|
transactionDate, txnExternalId);
|
||||||
|
|
||||||
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
|
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
|
||||||
|
refundTransaction);
|
||||||
|
|
||||||
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
|
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
|
||||||
.map(LoanSupportedInterestRefundTypes::getTransactionType)
|
.map(LoanSupportedInterestRefundTypes::getTransactionType)
|
||||||
@@ -1002,7 +1006,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
final List<LoanRepaymentScheduleInstallment> newInstallments = new ArrayList<>(loan.getRepaymentScheduleInstallments());
|
final List<LoanRepaymentScheduleInstallment> newInstallments = new ArrayList<>(loan.getRepaymentScheduleInstallments());
|
||||||
final MonetaryCurrency currency = loan.getCurrency();
|
final MonetaryCurrency currency = loan.getCurrency();
|
||||||
Money totalPrincipal = Money.zero(currency);
|
Money totalPrincipal = Money.zero(currency);
|
||||||
final Money[] balances = loan.retrieveIncomeForOverlappingPeriod(transactionDate);
|
final Money[] balances = loanBalanceService.retrieveIncomeForOverlappingPeriod(loan, transactionDate);
|
||||||
boolean isInterestComponent = true;
|
boolean isInterestComponent = true;
|
||||||
for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
for (final LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||||
if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) {
|
if (!DateUtils.isAfter(transactionDate, installment.getDueDate())) {
|
||||||
|
|||||||
+2
-1
@@ -1571,7 +1571,8 @@ public class LoanScheduleAssembler {
|
|||||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
||||||
if (loanScheduleModelPeriod instanceof LoanScheduleModelDisbursementPeriod) {
|
if (loanScheduleModelPeriod instanceof LoanScheduleModelDisbursementPeriod) {
|
||||||
for (final LoanCharge loanCharge : nonCompoundingCharges) {
|
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);
|
loanScheduleModelPeriod.addLoanCharges(loanCharge.getAmountOutstanding(), BigDecimal.ZERO);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -55,6 +55,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
|||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
private final LoanTransactionAssembler loanTransactionAssembler;
|
private final LoanTransactionAssembler loanTransactionAssembler;
|
||||||
private final LoanAccountService loanAccountService;
|
private final LoanAccountService loanAccountService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
@@ -93,7 +94,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
|||||||
return;
|
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
|
// Reverse accrual activities posted after the closure date
|
||||||
loan.getLoanTransactions(t -> t.isAccrualActivity() && !t.isReversed() && t.getDateOf().isAfter(closureDate))
|
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 TransactionTemplate transactionTemplate;
|
||||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||||
private final LoanChargeService loanChargeService;
|
private final LoanChargeService loanChargeService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* method adds accrual for batch job "Add Periodic Accrual Transactions" and add accruals api for Loan
|
* 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
|
// TODO implement progressive accrual case
|
||||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
|
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()
|
||||||
&& (loan.getAccruedTill() == null || !DateUtils.isEqual(foreClosureDate, loan.getAccruedTill()))) {
|
&& (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();
|
MonetaryCurrency currency = loan.getCurrency();
|
||||||
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
|
reverseTransactionsAfter(retrieveListOfAccrualTransactions(loan), foreClosureDate, false);
|
||||||
|
|
||||||
@@ -494,7 +495,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
Money interest = null;
|
Money interest = null;
|
||||||
final boolean isPastPeriod = isAfterPeriod(tillDate, installment);
|
final boolean isPastPeriod = isAfterPeriod(tillDate, installment);
|
||||||
final boolean isInPeriod = isInPeriod(tillDate, installment, false);
|
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());
|
interest = installment.getInterestCharged(currency).minus(installment.getCreditedInterest());
|
||||||
} else {
|
} else {
|
||||||
if (isInPeriod) { // first period first day is not accrued
|
if (isInPeriod) { // first period first day is not accrued
|
||||||
|
|||||||
+6
-5
@@ -61,6 +61,7 @@ public class LoanChargeAssembler {
|
|||||||
private final LoanChargeRepository loanChargeRepository;
|
private final LoanChargeRepository loanChargeRepository;
|
||||||
private final LoanProductRepository loanProductRepository;
|
private final LoanProductRepository loanProductRepository;
|
||||||
private final ExternalIdFactory externalIdFactory;
|
private final ExternalIdFactory externalIdFactory;
|
||||||
|
private final LoanChargeService loanChargeService;
|
||||||
|
|
||||||
public Set<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> disbursementDetails) {
|
public Set<LoanCharge> fromParsedJson(final JsonElement element, List<LoanDisbursementDetails> disbursementDetails) {
|
||||||
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
|
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
|
||||||
@@ -210,7 +211,7 @@ public class LoanChargeAssembler {
|
|||||||
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
|
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
|
||||||
|
|
||||||
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
|
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
|
||||||
loanCharge.update(amount, dueDate, numberOfRepayments);
|
loanChargeService.update(loanCharge, amount, dueDate, numberOfRepayments);
|
||||||
loanCharges.add(loanCharge);
|
loanCharges.add(loanCharge);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +297,7 @@ public class LoanChargeAssembler {
|
|||||||
if (percentage == null) {
|
if (percentage == null) {
|
||||||
percentage = chargeDefinition.getAmount();
|
percentage = chargeDefinition.getAmount();
|
||||||
}
|
}
|
||||||
loanCharge = LoanCharge.calculatePerInstallmentChargeAmount(loan,
|
loanCharge = loanChargeService.calculatePerInstallmentChargeAmount(loan,
|
||||||
ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()), percentage);
|
ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation()), percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +315,7 @@ public class LoanChargeAssembler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ExternalId externalId = externalIdFactory.createFromCommand(command, "externalId");
|
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);
|
chargePaymentMode, null, loanCharge, externalId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +325,7 @@ public class LoanChargeAssembler {
|
|||||||
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
|
public LoanCharge createNewWithoutLoan(final Charge chargeDefinition, final BigDecimal loanPrincipal, final BigDecimal amount,
|
||||||
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
|
final ChargeTimeType chargeTime, final ChargeCalculationType chargeCalculation, final LocalDate dueDate,
|
||||||
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
|
final ChargePaymentMode chargePaymentMode, final Integer numberOfRepayments, final ExternalId externalId) {
|
||||||
return new LoanCharge(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate, chargePaymentMode,
|
return loanChargeService.create(null, chargeDefinition, loanPrincipal, amount, chargeTime, chargeCalculation, dueDate,
|
||||||
numberOfRepayments, BigDecimal.ZERO, externalId);
|
chargePaymentMode, numberOfRepayments, BigDecimal.ZERO, externalId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-4
@@ -288,9 +288,9 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
.equals(loan.transactionProcessingStrategy())) {
|
.equals(loan.transactionProcessingStrategy())) {
|
||||||
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
|
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
|
||||||
// date, if yes trigger reprocess else no reprocessing
|
// date, if yes trigger reprocess else no reprocessing
|
||||||
LoanTransaction lastPaymentTransaction = loan.getLastTransactionForReprocessing();
|
final Optional<LocalDate> lastPaymentTransactionDate = loanTransactionRepository.findLastTransactionDateForReprocessing(loan);
|
||||||
if (lastPaymentTransaction != null
|
if (lastPaymentTransactionDate.isPresent()
|
||||||
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransaction.getTransactionDate())) {
|
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransactionDate.get())) {
|
||||||
reprocessRequired = false;
|
reprocessRequired = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,7 +475,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
final Map<String, Object> changes = new LinkedHashMap<>(3);
|
final Map<String, Object> changes = new LinkedHashMap<>(3);
|
||||||
if (loan.getActiveCharges().contains(loanCharge)) {
|
if (loan.getActiveCharges().contains(loanCharge)) {
|
||||||
final BigDecimal amount = loanChargeService.calculateAmountPercentageAppliedTo(loan, 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);
|
changes.putAll(loanChargeChanges);
|
||||||
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
loan.updateSummaryWithTotalFeeChargesDueAtDisbursement(loan.deriveSumTotalOfChargesDueAtDisbursement());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -244,8 +244,8 @@ public class LoanDisbursementService {
|
|||||||
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
if (TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
|
||||||
externalId = ExternalId.generate();
|
externalId = ExternalId.generate();
|
||||||
}
|
}
|
||||||
final LoanCharge loanCharge = new LoanCharge(loan, chargeDefinition, principal, null, null, null, expectedDisbursementDate,
|
final LoanCharge loanCharge = loanChargeService.create(loan, chargeDefinition, principal, null, null, null,
|
||||||
null, null, BigDecimal.ZERO, externalId);
|
expectedDisbursementDate, null, null, BigDecimal.ZERO, externalId);
|
||||||
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
|
LoanTrancheDisbursementCharge loanTrancheDisbursementCharge = new LoanTrancheDisbursementCharge(loanCharge,
|
||||||
disbursementDetails);
|
disbursementDetails);
|
||||||
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
|
loanCharge.updateLoanTrancheDisbursementCharge(loanTrancheDisbursementCharge);
|
||||||
|
|||||||
+5
-2
@@ -179,6 +179,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
private final LoanForeclosureValidator loanForeclosureValidator;
|
private final LoanForeclosureValidator loanForeclosureValidator;
|
||||||
private final LoanTransactionMapper loanTransactionMapper;
|
private final LoanTransactionMapper loanTransactionMapper;
|
||||||
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
private final LoanTransactionProcessingService loadTransactionProcessingService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoanAccountData retrieveOne(final Long loanId) {
|
public LoanAccountData retrieveOne(final Long loanId) {
|
||||||
@@ -2088,7 +2089,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
|
|
||||||
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
|
final LocalDate earliestUnpaidInstallmentDate = DateUtils.getBusinessLocalDate();
|
||||||
|
|
||||||
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loan.fetchLoanForeclosureDetail(transactionDate);
|
final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment = loanBalanceService.fetchLoanForeclosureDetail(loan,
|
||||||
|
transactionDate);
|
||||||
BigDecimal unrecognizedIncomePortion = null;
|
BigDecimal unrecognizedIncomePortion = null;
|
||||||
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
|
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
|
||||||
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
|
||||||
@@ -2236,7 +2238,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
return loanRepositoryWrapper.existsByLoanId(loanId);
|
return loanRepositoryWrapper.existsByLoanId(loanId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoanTransaction deriveDefaultInterestWaiverTransaction(Loan loan) {
|
private LoanTransaction deriveDefaultInterestWaiverTransaction(final Loan loan) {
|
||||||
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
|
final Money totalInterestOutstanding = loan.getTotalInterestOutstandingOnLoan();
|
||||||
Money possibleInterestToWaive = totalInterestOutstanding.copy();
|
Money possibleInterestToWaive = totalInterestOutstanding.copy();
|
||||||
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||||
@@ -2260,4 +2262,5 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
|
|||||||
return LoanTransaction.waiver(loan.getOffice(), loan, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
|
return LoanTransaction.waiver(loan.getOffice(), loan, possibleInterestToWaive, transactionDate, possibleInterestToWaive,
|
||||||
possibleInterestToWaive.zero(), ExternalId.empty());
|
possibleInterestToWaive.zero(), ExternalId.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -61,6 +61,8 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
|||||||
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessorFactory;
|
||||||
private final LoanTermVariationsMapper loanMapper;
|
private final LoanTermVariationsMapper loanMapper;
|
||||||
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
|
public boolean canProcessLatestTransactionOnly(Loan loan, LoanTransaction loanTransaction,
|
||||||
@@ -86,7 +88,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
|||||||
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
ProgressiveTransactionCtx progressiveContext = new ProgressiveTransactionCtx(loan.getCurrency(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), new MoneyHolder(loan.getTotalOverpaidAsMoney()),
|
||||||
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
|
new ChangedTransactionDetail(), savedModel.orElse(null), getTotalRefundInterestAmount(loan));
|
||||||
progressiveContext.getAlreadyProcessedTransactions().addAll(loan.retrieveListOfTransactionsForReprocessing());
|
progressiveContext.getAlreadyProcessedTransactions().addAll(loanTransactionService.retrieveListOfTransactionsForReprocessing(loan));
|
||||||
progressiveContext.setChargedOff(loan.isChargedOff());
|
progressiveContext.setChargedOff(loan.isChargedOff());
|
||||||
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
|
ChangedTransactionDetail result = advancedProcessor.processLatestTransaction(loanTransaction, progressiveContext);
|
||||||
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
if (savedModel.isPresent() && !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
|
||||||
@@ -158,7 +160,9 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
|||||||
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
|
public Optional<ChangedTransactionDetail> processPostDisbursementTransactions(Loan loan) {
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor(
|
||||||
loan.getTransactionProcessingStrategyCode());
|
loan.getTransactionProcessingStrategyCode());
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
|
||||||
|
.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
|
|
||||||
final List<LoanTransaction> copyTransactions = new ArrayList<>();
|
final List<LoanTransaction> copyTransactions = new ArrayList<>();
|
||||||
|
|
||||||
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
|
if (allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||||
@@ -173,7 +177,7 @@ public class LoanTransactionProcessingServiceImpl implements LoanTransactionProc
|
|||||||
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
loan.getDisbursementDate(), copyTransactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||||
loan.getActiveCharges());
|
loan.getActiveCharges());
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
|
||||||
return Optional.of(changedTransactionDetail);
|
return Optional.of(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-130
@@ -287,6 +287,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||||
private final LoanMapper loanMapper;
|
private final LoanMapper loanMapper;
|
||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
@@ -572,7 +574,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSED,
|
loanTransactionValidator.validateActivityNotBeforeClientOrGroupTransferDate(loan, LoanEvent.LOAN_DISBURSED,
|
||||||
actualDisbursementDate1);
|
actualDisbursementDate1);
|
||||||
loanDisbursementService.handleDisbursementTransaction(loan, actualDisbursementDate1, paymentDetail1);
|
loanDisbursementService.handleDisbursementTransaction(loan, actualDisbursementDate1, paymentDetail1);
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
final Money interestApplied = Money.of(loan.getCurrency(), loan.getSummary().getTotalInterestCharged());
|
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()) {
|
if (loan.getLoanProduct().isMultiDisburseLoan() || loan.isProgressiveSchedule()) {
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loanTransactionService
|
||||||
|
.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||||
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
reprocessLoanTransactionsService.reprocessTransactions(loan);
|
||||||
}
|
}
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan);
|
loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, loan);
|
||||||
@@ -972,8 +975,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
newInterestPaymentWaiverTransaction.updateLoan(loan);
|
newInterestPaymentWaiverTransaction.updateLoan(loan);
|
||||||
|
|
||||||
final boolean isTransactionChronologicallyLatest = loan
|
final boolean isTransactionChronologicallyLatest = loanTransactionService.isChronologicallyLatestRepaymentOrWaiver(loan,
|
||||||
.isChronologicallyLatestRepaymentOrWaiver(newInterestPaymentWaiverTransaction);
|
newInterestPaymentWaiverTransaction);
|
||||||
|
|
||||||
if (newInterestPaymentWaiverTransaction.isNotZero()) {
|
if (newInterestPaymentWaiverTransaction.isNotZero()) {
|
||||||
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
loan.addLoanTransaction(newInterestPaymentWaiverTransaction);
|
||||||
@@ -1412,7 +1415,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
Money unrecognizedIncome = transactionAmountAsMoney.zero();
|
Money unrecognizedIncome = transactionAmountAsMoney.zero();
|
||||||
Money interestComponent = transactionAmountAsMoney;
|
Money interestComponent = transactionAmountAsMoney;
|
||||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||||
Money receivableInterest = loan.getReceivableInterest(transactionDate);
|
Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
|
||||||
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
|
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
|
||||||
interestComponent = receivableInterest;
|
interestComponent = receivableInterest;
|
||||||
unrecognizedIncome = transactionAmountAsMoney.minus(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) {
|
private void removeLoanCycle(final Loan loan) {
|
||||||
final List<Loan> loansToUpdate;
|
final List<Loan> loansToUpdate;
|
||||||
if (loan.isGroupLoan()) {
|
if (loan.isGroupLoan()) {
|
||||||
@@ -2924,7 +2814,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null);
|
||||||
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
|
loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
final List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> loanTransactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
loanTransactions.add(chargeOffTransaction);
|
loanTransactions.add(chargeOffTransaction);
|
||||||
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
|
reprocessLoanTransactionsService.reprocessParticularTransactions(loan, loanTransactions);
|
||||||
loan.addLoanTransaction(chargeOffTransaction);
|
loan.addLoanTransaction(chargeOffTransaction);
|
||||||
@@ -3117,9 +3007,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
loan.addLoanTransaction(chargebackTransaction);
|
loan.addLoanTransaction(chargebackTransaction);
|
||||||
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
|
if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) {
|
||||||
|
final List<LoanTransaction> transactions = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan);
|
||||||
loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(),
|
loanTransactionProcessingService.reprocessLoanTransactions(loan.getTransactionProcessingStrategyCode(),
|
||||||
loan.getDisbursementDate(), loan.retrieveListOfTransactionsForReprocessing(), loan.getCurrency(),
|
loan.getDisbursementDate(), transactions, loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
loan.getActiveCharges());
|
||||||
} else {
|
} else {
|
||||||
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
|
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), chargebackTransaction,
|
||||||
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
@@ -3215,7 +3106,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
|
|
||||||
loan.adjustNetDisbursalAmount(loan.getApprovedPrincipal());
|
loan.adjustNetDisbursalAmount(loan.getApprovedPrincipal());
|
||||||
actualChanges.put(ACTUAL_DISBURSEMENT_DATE, "");
|
actualChanges.put(ACTUAL_DISBURSEMENT_DATE, "");
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actualChanges;
|
return actualChanges;
|
||||||
@@ -3246,7 +3137,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||||
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
wrapper.reprocess(loan.getCurrency(), loan.getDisbursementDate(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reverseExistingTransactions(final Loan 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 TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
|
||||||
return Optional.of(loanTransaction);
|
return Optional.of(loanTransaction);
|
||||||
}
|
}
|
||||||
@@ -3383,7 +3274,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
changes.put("externalId", externalId);
|
changes.put("externalId", externalId);
|
||||||
loanTransaction = LoanTransaction.writeoff(loan, loan.getOffice(), closureDate, 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) {
|
if (!isLastTransaction) {
|
||||||
final String errorMessage = "The closing date of the loan must be on or after latest transaction date.";
|
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",
|
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 TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
} else if (totalOutstanding.isGreaterThanZero()) {
|
} else if (totalOutstanding.isGreaterThanZero()) {
|
||||||
final String errorMessage = "A loan with money outstanding cannot be closed";
|
final String errorMessage = "A loan with money outstanding cannot be closed";
|
||||||
throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage,
|
throw new InvalidLoanStateTransitionException("close", "loan.has.money.outstanding", errorMessage,
|
||||||
@@ -3403,8 +3295,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loan.isOverPaid()) {
|
if (loanBalanceService.isOverPaid(loan)) {
|
||||||
final Money totalLoanOverpayment = loan.calculateTotalOverpayment();
|
final Money totalLoanOverpayment = loanBalanceService.calculateTotalOverpayment(loan);
|
||||||
if (totalLoanOverpayment.isGreaterThanZero() && loan.getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
|
if (totalLoanOverpayment.isGreaterThanZero() && loan.getInArrearsTolerance().isGreaterThanOrEqualTo(totalLoanOverpayment)) {
|
||||||
// TODO - KW - technically should set somewhere that this loan
|
// TODO - KW - technically should set somewhere that this loan
|
||||||
// has 'overpaid' amount
|
// has 'overpaid' amount
|
||||||
@@ -3474,7 +3366,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
final LoanDisbursementDetails disbursementDetail = loan.getDisbursementDetails(lastTransactionDate,
|
final LoanDisbursementDetails disbursementDetail = loan.getDisbursementDetails(lastTransactionDate,
|
||||||
lastDisbursalTransaction.getAmount());
|
lastDisbursalTransaction.getAmount());
|
||||||
loan.updateLoanToLastDisbursalState(disbursementDetail);
|
loanBalanceService.updateLoanToLastDisbursalState(loan, disbursementDetail);
|
||||||
loan.getLoanTermVariations()
|
loan.getLoanTermVariations()
|
||||||
.removeIf(loanTermVariations -> (loanTermVariations.getTermType().isDueDateVariation()
|
.removeIf(loanTermVariations -> (loanTermVariations.getTermType().isDueDateVariation()
|
||||||
&& DateUtils.isAfter(loanTermVariations.fetchDateValue(), lastTransactionDate))
|
&& DateUtils.isAfter(loanTermVariations.fetchDateValue(), lastTransactionDate))
|
||||||
@@ -3579,4 +3471,118 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
}
|
}
|
||||||
return !statusEnum.hasStateOf(actualLoanStatus) || isMultiTrancheDisburse;
|
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 AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
|
||||||
private final EMICalculator emiCalculator;
|
private final EMICalculator emiCalculator;
|
||||||
private final LoanRepositoryWrapper loanRepository;
|
private final LoanRepositoryWrapper loanRepository;
|
||||||
|
private final LoanTransactionService loanTransactionService;
|
||||||
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
private final InterestScheduleModelRepositoryWrapper modelRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -80,8 +81,8 @@ public class ProgressiveLoanSummaryDataProvider extends CommonLoanSummaryDataPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
|
private ProgressiveLoanInterestScheduleModel calculateModel(Loan loan, LocalDate businessDate) {
|
||||||
List<LoanTransaction> transactionsToReprocess = loan.retrieveListOfTransactionsForReprocessing().stream()
|
final List<LoanTransaction> transactionsToReprocess = loanTransactionService.retrieveListOfTransactionsForReprocessing(loan)
|
||||||
.filter(t -> !t.isAccrualActivity()).toList();
|
.stream().filter(t -> !t.isAccrualActivity()).toList();
|
||||||
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> changedTransactionDetailProgressiveLoanInterestScheduleModelPair = advancedPaymentScheduleTransactionProcessor
|
||||||
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
|
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), businessDate, transactionsToReprocess, loan.getCurrency(),
|
||||||
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
|
||||||
|
|||||||
+22
-6
@@ -22,6 +22,8 @@ import java.time.LocalDate;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
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.LoanChargePaidBy;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
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.MoneyHolder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
|
||||||
@@ -49,15 +53,27 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
private final ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService;
|
||||||
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
private final LoanTransactionProcessingService loanTransactionProcessingService;
|
||||||
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
|
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
private final LoanTransactionRepository loanTransactionRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reprocessTransactions(final Loan loan) {
|
public void reprocessTransactions(final Loan loan) {
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing(loan);
|
||||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
||||||
allNonContraTransactionsPostDisbursement);
|
allNonContraTransactionsPostDisbursement);
|
||||||
handleChangedDetail(changedTransactionDetail);
|
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
|
@Override
|
||||||
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
|
public void reprocessParticularTransactions(final Loan loan, final List<LoanTransaction> loanTransactions) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
|
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loanTransactions);
|
||||||
@@ -66,8 +82,8 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) {
|
||||||
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan,
|
final List<LoanTransaction> transactions = retrieveListOfTransactionsForReprocessing(loan);
|
||||||
loan.retrieveListOfTransactionsForReprocessing());
|
final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, transactions);
|
||||||
handleChangedDetail(changedTransactionDetail);
|
handleChangedDetail(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +117,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
loan.getLoanCharges().remove(loanCharge);
|
loan.getLoanCharges().remove(loanCharge);
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(final Loan loan, final LoanCharge loanCharge) {
|
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();
|
.map(TransactionChangeData::getNewTransaction).peek(transaction -> transaction.updateLoan(loan)).toList();
|
||||||
loan.getLoanTransactions().addAll(newTransactions);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
|
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
handleChangedDetail(changedTransactionDetail);
|
handleChangedDetail(changedTransactionDetail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +201,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
final List<LoanTransaction> newTransactions = changedTransactionDetail.getTransactionChanges().stream()
|
||||||
.map(TransactionChangeData::getNewTransaction).toList();
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
loan.getLoanTransactions().addAll(newTransactions);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
loan.updateLoanSummaryDerivedFields();
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
return changedTransactionDetail;
|
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.serialization.LoanTransactionValidator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualTransactionBusinessEventService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
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.LoanDownPaymentHandlerService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanJournalEntryPoster;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
|
||||||
@@ -91,6 +92,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
|||||||
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
private final LoanAccrualsProcessingService loanAccrualsProcessingService;
|
||||||
private final LoanChargeValidator loanChargeValidator;
|
private final LoanChargeValidator loanChargeValidator;
|
||||||
private final LoanJournalEntryPoster journalEntryPoster;
|
private final LoanJournalEntryPoster journalEntryPoster;
|
||||||
|
private final LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
|
public CommandProcessingResult adjustLoanTransaction(Loan loan, LoanTransaction transactionToAdjust, LoanAdjustmentParameter parameter,
|
||||||
@@ -112,7 +114,7 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
|||||||
Money unrecognizedIncome = transactionAmountAsMoney.zero();
|
Money unrecognizedIncome = transactionAmountAsMoney.zero();
|
||||||
Money interestComponent = transactionAmountAsMoney;
|
Money interestComponent = transactionAmountAsMoney;
|
||||||
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
|
||||||
Money receivableInterest = loan.getReceivableInterest(transactionDate);
|
Money receivableInterest = loanBalanceService.getReceivableInterest(loan, transactionDate);
|
||||||
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
|
if (transactionAmountAsMoney.isGreaterThan(receivableInterest)) {
|
||||||
interestComponent = receivableInterest;
|
interestComponent = receivableInterest;
|
||||||
unrecognizedIncome = transactionAmountAsMoney.minus(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.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
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.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.ProgressiveLoanInterestRefundServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
|
import org.apache.fineract.portfolio.loanaccount.service.schedule.LoanScheduleComponent;
|
||||||
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
|
||||||
@@ -48,63 +51,77 @@ public class LoanAccountAutoStarter {
|
|||||||
@Bean
|
@Bean
|
||||||
@Conditional(CreocoreLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(CreocoreLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public CreocoreLoanRepaymentScheduleTransactionProcessor creocoreLoanRepaymentScheduleTransactionProcessor(
|
public CreocoreLoanRepaymentScheduleTransactionProcessor creocoreLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new CreocoreLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(EarlyRepaymentLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor earlyPaymentLoanRepaymentScheduleTransactionProcessor(
|
public EarlyPaymentLoanRepaymentScheduleTransactionProcessor earlyPaymentLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new EarlyPaymentLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(MifosStandardLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public FineractStyleLoanRepaymentScheduleTransactionProcessor fineractStyleLoanRepaymentScheduleTransactionProcessor(
|
public FineractStyleLoanRepaymentScheduleTransactionProcessor fineractStyleLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new FineractStyleLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(HeavensFamilyLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor heavensFamilyLoanRepaymentScheduleTransactionProcessor(
|
public HeavensFamilyLoanRepaymentScheduleTransactionProcessor heavensFamilyLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new HeavensFamilyLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(InterestPrincipalPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor interestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
public InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor interestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
|
||||||
|
loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(PrincipalInterestPenaltiesFeesLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor principalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
public PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor principalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
|
||||||
|
loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(RBIIndiaLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(ExternalIdFactory externalIdFactory) {
|
public RBILoanRepaymentScheduleTransactionProcessor rbiLoanRepaymentScheduleTransactionProcessor(
|
||||||
return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new RBILoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor duePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(
|
public DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor duePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
|
||||||
|
loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessorCondition.class)
|
||||||
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor duePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(
|
public DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor duePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(
|
||||||
ExternalIdFactory externalIdFactory) {
|
final ExternalIdFactory externalIdFactory, final LoanChargeValidator loanChargeValidator,
|
||||||
return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory);
|
final LoanBalanceService loanBalanceService) {
|
||||||
|
return new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(externalIdFactory, loanChargeValidator,
|
||||||
|
loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -117,11 +134,13 @@ public class LoanAccountAutoStarter {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
@Conditional(AdvancedPaymentScheduleTransactionProcessorCondition.class)
|
||||||
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(EMICalculator emiCalculator,
|
public AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator,
|
||||||
LoanRepositoryWrapper loanRepositoryWrapper,
|
final LoanRepositoryWrapper loanRepositoryWrapper,
|
||||||
@Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService, ExternalIdFactory externalIdFactory,
|
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
|
||||||
LoanScheduleComponent loanSchedule) {
|
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
|
||||||
|
final LoanTransactionService loanTransactionService, final LoanChargeValidator loanChargeValidator,
|
||||||
|
final LoanBalanceService loanBalanceService) {
|
||||||
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, progressiveLoanInterestRefundService,
|
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.LoanArrearsAgingServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
|
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.LoanCalculateRepaymentPastDueService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
|
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.LoanTransactionAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionRelationReadService;
|
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.LoanUtilService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanWritePlatformServiceJpaRepositoryImpl;
|
||||||
@@ -268,11 +270,11 @@ public class LoanAccountConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(LoanChargeAssembler.class)
|
@ConditionalOnMissingBean(LoanChargeAssembler.class)
|
||||||
public LoanChargeAssembler loanChargeAssembler(
|
public LoanChargeAssembler loanChargeAssembler(final FromJsonHelper fromApiJsonHelper, final ChargeRepositoryWrapper chargeRepository,
|
||||||
|
final LoanChargeRepository loanChargeRepository, final LoanProductRepository loanProductRepository,
|
||||||
FromJsonHelper fromApiJsonHelper, ChargeRepositoryWrapper chargeRepository, LoanChargeRepository loanChargeRepository,
|
final ExternalIdFactory externalIdFactory, final LoanChargeService loanChargeService) {
|
||||||
LoanProductRepository loanProductRepository, ExternalIdFactory externalIdFactory) {
|
return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory,
|
||||||
return new LoanChargeAssembler(fromApiJsonHelper, chargeRepository, loanChargeRepository, loanProductRepository, externalIdFactory);
|
loanChargeService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -331,14 +333,14 @@ public class LoanAccountConfiguration {
|
|||||||
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
|
DelinquencyReadPlatformService delinquencyReadPlatformService, LoanTransactionRepository loanTransactionRepository,
|
||||||
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
|
LoanChargePaidByReadService loanChargePaidByReadService, LoanTransactionRelationReadService loanTransactionRelationReadService,
|
||||||
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
|
LoanForeclosureValidator loanForeclosureValidator, LoanTransactionMapper loanTransactionMapper,
|
||||||
LoanTransactionProcessingService loanTransactionProcessingService) {
|
LoanTransactionProcessingService loanTransactionProcessingService, LoanBalanceService loanBalanceService) {
|
||||||
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
|
return new LoanReadPlatformServiceImpl(jdbcTemplate, context, loanRepositoryWrapper, applicationCurrencyRepository,
|
||||||
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
|
loanProductReadPlatformService, clientReadPlatformService, groupReadPlatformService, loanDropdownReadPlatformService,
|
||||||
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
|
fundReadPlatformService, chargeReadPlatformService, codeValueReadPlatformService, calendarReadPlatformService,
|
||||||
staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService,
|
staffReadPlatformService, paginationHelper, paymentTypeReadPlatformService, floatingRatesReadPlatformService,
|
||||||
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
|
loanUtilService, configurationDomainService, accountDetailsReadPlatformService, columnValidator, sqlGenerator,
|
||||||
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
|
delinquencyReadPlatformService, loanTransactionRepository, loanChargePaidByReadService, loanTransactionRelationReadService,
|
||||||
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService);
|
loanForeclosureValidator, loanTransactionMapper, loanTransactionProcessingService, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -402,7 +404,8 @@ public class LoanAccountConfiguration {
|
|||||||
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService,
|
ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanAccountService loanAccountService,
|
||||||
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService,
|
LoanJournalEntryPoster journalEntryPoster, LoanAdjustmentService loanAdjustmentService,
|
||||||
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper,
|
LoanAccountingBridgeMapper loanAccountingBridgeMapper, LoanMapper loanMapper,
|
||||||
LoanTransactionProcessingService loanTransactionProcessingService) {
|
LoanTransactionProcessingService loanTransactionProcessingService, final LoanBalanceService loanBalanceService,
|
||||||
|
LoanTransactionService loanTransactionService) {
|
||||||
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
return new LoanWritePlatformServiceJpaRepositoryImpl(context, loanTransactionValidator, loanUpdateCommandFromApiJsonDeserializer,
|
||||||
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
loanRepositoryWrapper, loanAccountDomainService, noteRepository, loanTransactionRepository,
|
||||||
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
loanTransactionRelationRepository, loanAssembler, journalEntryWritePlatformService, calendarInstanceRepository,
|
||||||
@@ -417,7 +420,7 @@ public class LoanAccountConfiguration {
|
|||||||
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
|
loanTransactionAssembler, loanAccrualsProcessingService, loanOfficerValidator, loanDownPaymentTransactionValidator,
|
||||||
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService,
|
loanDisbursementService, loanScheduleService, loanChargeValidator, loanOfficerService, reprocessLoanTransactionsService,
|
||||||
loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper,
|
loanAccountService, journalEntryPoster, loanAdjustmentService, loanAccountingBridgeMapper, loanMapper,
|
||||||
loanTransactionProcessingService);
|
loanTransactionProcessingService, loanBalanceService, loanTransactionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -434,10 +437,12 @@ public class LoanAccountConfiguration {
|
|||||||
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService,
|
LoanDownPaymentTransactionValidator loanDownPaymentTransactionValidator, LoanScheduleService loanScheduleService,
|
||||||
LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator,
|
LoanRefundService loanRefundService, LoanRefundValidator loanRefundValidator,
|
||||||
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
|
ReprocessLoanTransactionsService reprocessLoanTransactionsService,
|
||||||
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine,
|
||||||
|
LoanBalanceService loanBalanceService, LoanTransactionService loanTransactionService) {
|
||||||
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
|
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
|
||||||
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
|
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
|
||||||
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine);
|
reprocessLoanTransactionsService, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService,
|
||||||
|
loanTransactionService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -457,9 +462,10 @@ public class LoanAccountConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnMissingBean(LoanChargeService.class)
|
@ConditionalOnMissingBean(LoanChargeService.class)
|
||||||
public LoanChargeService loanChargeService(LoanChargeValidator loanChargeValidator,
|
public LoanChargeService loanChargeService(final LoanChargeValidator loanChargeValidator,
|
||||||
LoanTransactionProcessingService loanTransactionProcessingService, LoanLifecycleStateMachine loanLifecycleStateMachine) {
|
final LoanTransactionProcessingService loanTransactionProcessingService,
|
||||||
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine);
|
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanBalanceService loanBalanceService) {
|
||||||
|
return new LoanChargeService(loanChargeValidator, loanTransactionProcessingService, loanLifecycleStateMachine, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge;
|
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.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
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.LoanChargeReadPlatformService;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
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.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -122,6 +127,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private AvroDateTimeMapper mapper;
|
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 MockedStatic<MoneyHelper> moneyHelper = Mockito.mockStatic(MoneyHelper.class);
|
||||||
|
|
||||||
private static final String CUSTOM_DATA_PREFIX = "test_data_loan_delinquency_range_business_event";
|
private static final String CUSTOM_DATA_PREFIX = "test_data_loan_delinquency_range_business_event";
|
||||||
@@ -275,7 +283,7 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
.thenReturn(installmentDelinquencyTags);
|
.thenReturn(installmentDelinquencyTags);
|
||||||
|
|
||||||
when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
|
when(loanForProcessing.getLoanCharges()).thenAnswer(a -> repaymentScheduleInstallments.get(0).getInstallmentCharges().stream()
|
||||||
.map(c -> c.getLoanCharge()).collect(Collectors.toList()));
|
.map(LoanInstallmentCharge::getLoanCharge).collect(Collectors.toSet()));
|
||||||
|
|
||||||
// when
|
// when
|
||||||
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
|
LoanAccountDelinquencyRangeDataV1 data = (LoanAccountDelinquencyRangeDataV1) serializer.toAvroDTO(event);
|
||||||
@@ -431,8 +439,9 @@ public class LoanAccountDelinquencyRangeEventSerializerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) {
|
private LoanCharge buildLoanCharge(Loan loan, BigDecimal amount, Charge charge) {
|
||||||
LoanCharge loanCharge = new LoanCharge(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE, ChargeCalculationType.FLAT,
|
LoanCharge loanCharge = loanChargeService.create(loan, charge, amount, amount, ChargeTimeType.SPECIFIED_DUE_DATE,
|
||||||
LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100), ExternalId.generate());
|
ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1, new BigDecimal(100),
|
||||||
|
ExternalId.generate());
|
||||||
ReflectionTestUtils.setField(loanCharge, "id", 1L);
|
ReflectionTestUtils.setField(loanCharge, "id", 1L);
|
||||||
return loanCharge;
|
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.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
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.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -48,6 +49,9 @@ class DefaultLoanLifecycleStateMachineTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private BusinessEventNotifierService businessEventNotifierService;
|
private BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
private DefaultLoanLifecycleStateMachine underTest;
|
private DefaultLoanLifecycleStateMachine underTest;
|
||||||
|
|
||||||
private MockedStatic<MoneyHelper> moneyHelperStatic;
|
private MockedStatic<MoneyHelper> moneyHelperStatic;
|
||||||
@@ -58,7 +62,7 @@ class DefaultLoanLifecycleStateMachineTest {
|
|||||||
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
moneyHelperStatic = Mockito.mockStatic(MoneyHelper.class);
|
||||||
moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
|
moneyHelperStatic.when(MoneyHelper::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
|
||||||
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
|
moneyHelperStatic.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
|
||||||
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService);
|
underTest = new DefaultLoanLifecycleStateMachine(businessEventNotifierService, loanBalanceService);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@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.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
|
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.LoanProduct;
|
||||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||||
import org.apache.fineract.portfolio.rate.domain.Rate;
|
import org.apache.fineract.portfolio.rate.domain.Rate;
|
||||||
@@ -61,7 +63,7 @@ public class LoanBuilder {
|
|||||||
private Staff loanOfficer;
|
private Staff loanOfficer;
|
||||||
private CodeValue loanPurpose;
|
private CodeValue loanPurpose;
|
||||||
private LoanRepaymentScheduleTransactionProcessor transactionProcessor = new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
private LoanRepaymentScheduleTransactionProcessor transactionProcessor = new InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor(
|
||||||
mock(ExternalIdFactory.class));
|
mock(ExternalIdFactory.class), mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
|
||||||
private LoanProductRelatedDetail loanRepaymentScheduleDetail;
|
private LoanProductRelatedDetail loanRepaymentScheduleDetail;
|
||||||
private LoanStatus loanStatus = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
|
private LoanStatus loanStatus = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
|
||||||
private LoanSubStatus loanSubStatus;
|
private LoanSubStatus loanSubStatus;
|
||||||
|
|||||||
+18
-3
@@ -265,8 +265,23 @@ public class LoanTest {
|
|||||||
* @return the {@link LoanCharge}
|
* @return the {@link LoanCharge}
|
||||||
*/
|
*/
|
||||||
private LoanCharge buildLoanCharge() {
|
private LoanCharge buildLoanCharge() {
|
||||||
return new LoanCharge(mock(Loan.class), mock(Charge.class), new BigDecimal(100), new BigDecimal(100),
|
final LoanCharge loanCharge = new LoanCharge();
|
||||||
ChargeTimeType.TRANCHE_DISBURSEMENT, ChargeCalculationType.FLAT, LocalDate.of(2022, 6, 27), ChargePaymentMode.REGULAR, 1,
|
|
||||||
new BigDecimal(100), ExternalId.generate());
|
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.eq;
|
||||||
import static org.mockito.ArgumentMatchers.refEq;
|
import static org.mockito.ArgumentMatchers.refEq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
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.ActionContext;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
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.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
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.AfterAll;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@@ -99,7 +103,8 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(null);
|
underTest = new DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
|
||||||
|
mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
|
||||||
Mockito.reset(charges, transactionMappings);
|
Mockito.reset(charges, transactionMappings);
|
||||||
|
|
||||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
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.eq;
|
||||||
import static org.mockito.ArgumentMatchers.refEq;
|
import static org.mockito.ArgumentMatchers.refEq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
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.ActionContext;
|
||||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||||
import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
|
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.infrastructure.core.service.ThreadLocalContextUtil;
|
||||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
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.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
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.AfterAll;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
@@ -100,7 +104,8 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
|
|||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(null);
|
underTest = new DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactionProcessor(mock(ExternalIdFactory.class),
|
||||||
|
mock(LoanChargeValidator.class), mock(LoanBalanceService.class));
|
||||||
Mockito.reset(charges, transactionMappings);
|
Mockito.reset(charges, transactionMappings);
|
||||||
|
|
||||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||||
|
|||||||
+8
-1
@@ -111,13 +111,20 @@ public class LoanDownPaymentHandlerServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private LoanLifecycleStateMachine loanLifecycleStateMachine;
|
private LoanLifecycleStateMachine loanLifecycleStateMachine;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanBalanceService loanBalanceService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private LoanTransactionService loanTransactionService;
|
||||||
|
|
||||||
private LoanDownPaymentHandlerServiceImpl underTest;
|
private LoanDownPaymentHandlerServiceImpl underTest;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
|
underTest = new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService,
|
||||||
loanDownPaymentTransactionValidator, loanScheduleService, loanRefundService, loanRefundValidator,
|
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::getMathContext).thenReturn(new MathContext(12, RoundingMode.UP));
|
||||||
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
|
moneyHelper.when(MoneyHelper::getRoundingMode).thenReturn(RoundingMode.UP);
|
||||||
tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);
|
tempConfigServiceMock.when(TemporaryConfigurationServiceContainer::isExternalIdAutoGenerationEnabled).thenReturn(true);
|
||||||
|
|||||||
Reference in New Issue
Block a user