FINERACT-1981: Repayment Schedule Interest Model with multi disbursement support, AdvPymnTxProcessor fix
This commit is contained in:
committed by
Adam Saghy
parent
26388ea518
commit
69dd19c74b
+9
-9
@@ -111,8 +111,8 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.TransactionCtx;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.InvalidRefundDateException;
|
||||
@@ -801,8 +801,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
final Set<LoanCharge> loanCharges = new HashSet<>(1);
|
||||
loanCharges.add(charge);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment,
|
||||
new TransactionCtx(getCurrency(), chargePaymentInstallments, loanCharges, new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment, new TransactionCtx(getCurrency(),
|
||||
chargePaymentInstallments, loanCharges, new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
|
||||
@@ -1887,7 +1887,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
|
||||
repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate);
|
||||
repaymentPeriod.updateObligationsMet(loanCurrency(), actualDisbursementDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2395,7 +2395,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
if (isTransactionChronologicallyLatest && adjustedTransaction == null
|
||||
&& (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled()) && !isForeclosure()) {
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
reprocess = false;
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
|
||||
@@ -2843,7 +2843,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
addLoanTransaction(loanTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
@@ -2945,7 +2945,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
addLoanTransaction(loanTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
} else if (totalOutstanding.isGreaterThanZero()) {
|
||||
@@ -4858,7 +4858,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
// If it's a refund
|
||||
if (adjustedTransaction == null) {
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
} else {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
@@ -4888,7 +4888,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
addLoanTransaction(chargebackTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
if (!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), loanLifecycleStateMachine)) {
|
||||
|
||||
+19
-2
@@ -743,10 +743,13 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
|
||||
}
|
||||
|
||||
public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
|
||||
public void updateObligationsMet(final MonetaryCurrency currency, final LocalDate transactionDate) {
|
||||
if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
|
||||
this.obligationsMet = true;
|
||||
this.obligationsMetOnDate = actualDisbursementDate;
|
||||
this.obligationsMetOnDate = transactionDate;
|
||||
} else if (this.obligationsMet && !getTotalOutstanding(currency).isZero()) {
|
||||
this.obligationsMet = false;
|
||||
this.obligationsMetOnDate = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,6 +846,15 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
|
||||
}
|
||||
|
||||
public void addToInterest(final LocalDate transactionDate, final Money transactionAmount) {
|
||||
if (this.interestCharged == null) {
|
||||
this.interestCharged = transactionAmount.getAmount();
|
||||
} else {
|
||||
this.interestCharged = this.interestCharged.add(transactionAmount.getAmount());
|
||||
}
|
||||
checkIfRepaymentPeriodObligationsAreMet(transactionDate, transactionAmount.getCurrency());
|
||||
}
|
||||
|
||||
public void addToCreditedPrincipal(final BigDecimal amount) {
|
||||
if (this.creditedPrincipal == null) {
|
||||
this.creditedPrincipal = amount;
|
||||
@@ -1051,6 +1063,11 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
resetDerivedComponents();
|
||||
resetPrincipalDue();
|
||||
resetChargesCharged();
|
||||
resetInterestDue();
|
||||
}
|
||||
|
||||
public void resetInterestDue() {
|
||||
this.interestCharged = null;
|
||||
}
|
||||
|
||||
public void resetPrincipalDue() {
|
||||
|
||||
+4
-3
@@ -83,7 +83,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
currentInstallment.updateDerivedFields(currency, disbursementDate);
|
||||
currentInstallment.updateObligationsMet(currency, disbursementDate);
|
||||
}
|
||||
|
||||
// re-process loan charges over repayment periods (picking up on waived
|
||||
@@ -164,7 +164,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||
// pass through for new transactions
|
||||
if (loanTransaction.getId() == null) {
|
||||
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder));
|
||||
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||
loanTransaction.adjustInterestComponent(currency);
|
||||
} else {
|
||||
/**
|
||||
@@ -175,7 +175,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
// Reset derived component of new loan transaction and
|
||||
// re-process transaction
|
||||
processLatestTransaction(newLoanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder));
|
||||
processLatestTransaction(newLoanTransaction,
|
||||
new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||
newLoanTransaction.adjustInterestComponent(currency);
|
||||
/**
|
||||
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
||||
|
||||
-18
@@ -21,8 +21,6 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
@@ -32,22 +30,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public interface LoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
class TransactionCtx {
|
||||
|
||||
private final MonetaryCurrency currency;
|
||||
private final List<LoanRepaymentScheduleInstallment> installments;
|
||||
private final Set<LoanCharge> charges;
|
||||
private final MoneyHolder overpaymentHolder;
|
||||
private final ChangedTransactionDetail changedTransactionDetail;
|
||||
|
||||
public TransactionCtx(MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges,
|
||||
MoneyHolder overpaymentHolder) {
|
||||
this(currency, installments, charges, overpaymentHolder, null);
|
||||
}
|
||||
}
|
||||
|
||||
String getCode();
|
||||
|
||||
String getName();
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class TransactionCtx {
|
||||
|
||||
private final MonetaryCurrency currency;
|
||||
private final List<LoanRepaymentScheduleInstallment> installments;
|
||||
private final Set<LoanCharge> charges;
|
||||
private final MoneyHolder overpaymentHolder;
|
||||
private final ChangedTransactionDetail changedTransactionDetail;
|
||||
}
|
||||
+3
-2
@@ -2001,8 +2001,9 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
List<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
if (!loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal(),
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
|
||||
-7
@@ -37,13 +37,6 @@ public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleMo
|
||||
private final BigDecimal chargesDueAtTimeOfDisbursement;
|
||||
private boolean isEMIFixedSpecificToInstallment = false;
|
||||
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
|
||||
return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
|
||||
}
|
||||
|
||||
public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed,
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
return new LoanScheduleModelDisbursementPeriod(null, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement);
|
||||
|
||||
+10
-2
@@ -33,11 +33,11 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
|
||||
@Getter
|
||||
public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
private int periodNumber;
|
||||
private final LocalDate fromDate;
|
||||
private final LocalDate dueDate;
|
||||
private Money principalDue;
|
||||
private final Money outstandingLoanBalance;
|
||||
private Money outstandingLoanBalance;
|
||||
private Money interestDue;
|
||||
private Money feeChargesDue;
|
||||
private Money penaltyChargesDue;
|
||||
@@ -166,6 +166,14 @@ public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModel
|
||||
this.totalDue = this.totalDue.plus(interestDue);
|
||||
}
|
||||
|
||||
public void setPeriodNumber(int periodNumber) {
|
||||
this.periodNumber = periodNumber;
|
||||
}
|
||||
|
||||
public void setOutstandingLoanBalance(Money outstandingLoanBalance) {
|
||||
this.outstandingLoanBalance = outstandingLoanBalance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return this.loanCompoundingDetails;
|
||||
|
||||
-96
@@ -1,96 +0,0 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
|
||||
@Data
|
||||
@Accessors(fluent = true)
|
||||
public class PreGeneratedLoanSchedulePeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private Integer periodNumber;
|
||||
private boolean isRepaymentPeriod;
|
||||
private boolean isDownPaymentPeriod;
|
||||
private LocalDate periodFromDate;
|
||||
private LocalDate periodDueDate;
|
||||
private BigDecimal principalDue;
|
||||
private BigDecimal interestDue;
|
||||
private BigDecimal feeChargesDue;
|
||||
private BigDecimal penaltyChargesDue;
|
||||
private BigDecimal rescheduleInterestPortion;
|
||||
private boolean isRecalculatedInterestComponent;
|
||||
private boolean isEMIFixedSpecificToInstallment;
|
||||
|
||||
public PreGeneratedLoanSchedulePeriod(Integer periodNumber, LocalDate periodFromDate, LocalDate periodDueDate) {
|
||||
this.periodNumber = periodNumber;
|
||||
this.isRepaymentPeriod = true;
|
||||
this.isDownPaymentPeriod = false;
|
||||
this.periodFromDate = periodFromDate;
|
||||
this.periodDueDate = periodDueDate;
|
||||
this.principalDue = BigDecimal.ZERO;
|
||||
this.interestDue = BigDecimal.ZERO;
|
||||
this.feeChargesDue = BigDecimal.ZERO;
|
||||
this.penaltyChargesDue = BigDecimal.ZERO;
|
||||
this.rescheduleInterestPortion = BigDecimal.ZERO;
|
||||
this.isRecalculatedInterestComponent = false;
|
||||
this.isEMIFixedSpecificToInstallment = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanSchedulePeriodData toData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addPrincipalAmount(Money principalDue) {
|
||||
this.principalDue = this.principalDue.add(principalDue.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addInterestAmount(Money interestDue) {
|
||||
this.interestDue = this.principalDue.add(interestDue.getAmount());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEMIFixedSpecificToInstallmentTrue() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
List<PreGeneratedLoanSchedulePeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
|
||||
List<LoanScheduleModelRepaymentPeriod> generateRepaymentPeriods(LocalDate scheduledDueDate, LoanApplicationTerms loanApplicationTerms,
|
||||
HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
|
||||
|
||||
Reference in New Issue
Block a user