Skip to content

Commit

Permalink
FINERACT-1981: Repayment Schedule Interest Model with multi disbursem…
Browse files Browse the repository at this point in the history
…ent support, AdvPymnTxProcessor fix
  • Loading branch information
janez89 authored and adamsaghy committed Aug 7, 2024
1 parent 26388ea commit 69dd19c
Show file tree
Hide file tree
Showing 34 changed files with 1,868 additions and 1,586 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5237,8 +5237,8 @@ Feature: Loan
| 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 |
| | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 |
| 4 | 15 | 31 January 2024 | | 312.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 |
| 5 | 15 | 15 February 2024 | | 0.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 |
| 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 |
| 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 |
Expand Down Expand Up @@ -5274,8 +5274,8 @@ Feature: Loan
| 2 | 15 | 16 January 2024 | 16 January 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 |
| | | 16 January 2024 | | 500.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 3 | 0 | 16 January 2024 | 16 January 2024 | 625.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 |
| 4 | 15 | 31 January 2024 | | 312.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 |
| 5 | 15 | 15 February 2024 | | 0.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 |
| 4 | 15 | 31 January 2024 | | 313.0 | 312.0 | 0.0 | 0.0 | 0.0 | 312.0 | 125.0 | 125.0 | 0.0 | 187.0 |
| 5 | 15 | 15 February 2024 | | 0.0 | 313.0 | 0.0 | 0.0 | 0.0 | 313.0 | 125.0 | 125.0 | 0.0 | 188.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 1000.0 | 0 | 0.0 | 0 | 1000.0 | 625.0 | 250.0 | 0 | 375.0 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2446,8 +2446,8 @@ Feature: Loan DownPayment
| 2 | 15 | 16 February 2024 | 16 February 2024 | 250.0 | 125.0 | 0.0 | 0.0 | 0.0 | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 |
| | | 20 February 2024 | | 625.0 | | | 0.0 | | 0.0 | 0.0 | | | |
| 3 | 0 | 20 February 2024 | 20 February 2024 | 719.0 | 156.0 | 0.0 | 0.0 | 0.0 | 156.0 | 156.0 | 0.0 | 0.0 | 0.0 |
| 4 | 15 | 02 March 2024 | 20 February 2024 | 360.0 | 359.0 | 0.0 | 0.0 | 0.0 | 359.0 | 359.0 | 359.0 | 0.0 | 0.0 |
| 5 | 15 | 17 March 2024 | 20 February 2024 | 0.0 | 360.0 | 0.0 | 0.0 | 0.0 | 360.0 | 360.0 | 360.0 | 0.0 | 0.0 |
| 4 | 15 | 02 March 2024 | 20 February 2024 | 359.0 | 360.0 | 0.0 | 0.0 | 0.0 | 360.0 | 360.0 | 360.0 | 0.0 | 0.0 |
| 5 | 15 | 17 March 2024 | 20 February 2024 | 0.0 | 359.0 | 0.0 | 0.0 | 0.0 | 359.0 | 359.0 | 359.0 | 0.0 | 0.0 |
Then Loan Repayment schedule has the following data in Total row:
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
| 1125.0 | 0.0 | 0.0 | 0.0 | 1125.0 | 1125.0 | 719.0 | 0.0 | 0.0 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@
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;
Expand Down Expand Up @@ -801,8 +801,8 @@ private void handleChargePaidTransaction(final LoanCharge charge, final LoanTran
}
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);
Expand Down Expand Up @@ -1887,7 +1887,7 @@ private boolean atLeastOnceDisbursed() {
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
repaymentPeriod.updateDerivedFields(loanCurrency(), actualDisbursementDate);
repaymentPeriod.updateObligationsMet(loanCurrency(), actualDisbursementDate);
}
}

Expand Down Expand Up @@ -2395,7 +2395,7 @@ private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(fi
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()) {
Expand Down Expand Up @@ -2843,7 +2843,7 @@ public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, fin

addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));

updateLoanSummaryDerivedFields();
}
Expand Down Expand Up @@ -2945,7 +2945,7 @@ public ChangedTransactionDetail close(final JsonCommand command, final LoanLifec

addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney())));
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));

updateLoanSummaryDerivedFields();
} else if (totalOutstanding.isGreaterThanZero()) {
Expand Down Expand Up @@ -4858,7 +4858,7 @@ private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction l
// 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(),
Expand Down Expand Up @@ -4888,7 +4888,7 @@ public void handleChargebackTransaction(final LoanTransaction chargebackTransact

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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,10 +743,13 @@ public void updateAccrualPortion(final Money interest, final Money feeCharges, f
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;
}
}

Expand Down Expand Up @@ -843,6 +846,15 @@ public void addToPrincipal(final LocalDate transactionDate, final Money transact
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;
Expand Down Expand Up @@ -1051,6 +1063,11 @@ public void resetBalances() {
resetDerivedComponents();
resetPrincipalDue();
resetChargesCharged();
resetInterestDue();
}

public void resetInterestDue() {
this.interestCharged = null;
}

public void resetPrincipalDue() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur

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
Expand Down Expand Up @@ -164,7 +164,7 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur
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 {
/**
Expand All @@ -175,7 +175,8 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
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;
Expand All @@ -32,22 +30,6 @@

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,24 @@
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanproduct.calc;
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor;

import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
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;

@Getter
public class RepaymentScheduleModel {
@Data
@AllArgsConstructor
public class TransactionCtx {

List<RepaymentPeriodModel> scheduleList = new ArrayList<>();

public void addRepaymentPeriodModel(final RepaymentPeriodModel repaymentPeriodModel) {
scheduleList.add(repaymentPeriodModel);
}
private final MonetaryCurrency currency;
private final List<LoanRepaymentScheduleInstallment> installments;
private final Set<LoanCharge> charges;
private final MoneyHolder overpaymentHolder;
private final ChangedTransactionDetail changedTransactionDetail;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2001,8 +2001,9 @@ private List<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementD
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@
@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;
Expand Down Expand Up @@ -166,6 +166,14 @@ public void addInterestAmount(Money interestDue) {
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;
Expand Down
Loading

0 comments on commit 69dd19c

Please sign in to comment.