FINERACT-2059: Support reverse-replay of Re-age transaction
This commit is contained in:
+16
@@ -25,6 +25,7 @@ import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import java.math.BigDecimal;
|
||||
@@ -47,6 +48,7 @@ import org.apache.fineract.organisation.office.domain.Office;
|
||||
import org.apache.fineract.portfolio.account.data.AccountTransferData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
|
||||
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
|
||||
@@ -135,6 +137,9 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "fromTransaction")
|
||||
private Set<LoanTransactionRelation> loanTransactionRelations = new HashSet<>();
|
||||
|
||||
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "loanTransaction")
|
||||
private LoanReAgeParameter loanReAgeParameter;
|
||||
|
||||
protected LoanTransaction() {}
|
||||
|
||||
public static LoanTransaction incomePosting(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||
@@ -292,6 +297,9 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (LoanTransactionType.CHARGE_PAYMENT.equals(loanTransaction.getTypeOf())) {
|
||||
newTransaction.getLoanChargesPaid().addAll(loanTransaction.getLoanChargesPaid());
|
||||
}
|
||||
if (LoanTransactionType.REAGE.equals(loanTransaction.getTypeOf())) {
|
||||
newTransaction.setLoanReAgeParameter(loanTransaction.getLoanReAgeParameter().getCopy(newTransaction));
|
||||
}
|
||||
return newTransaction;
|
||||
}
|
||||
|
||||
@@ -1000,6 +1008,14 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
return DateUtils.isEqual(getTransactionDate(), date);
|
||||
}
|
||||
|
||||
public LoanReAgeParameter getLoanReAgeParameter() {
|
||||
return loanReAgeParameter;
|
||||
}
|
||||
|
||||
public void setLoanReAgeParameter(LoanReAgeParameter loanReAgeParameter) {
|
||||
this.loanReAgeParameter = loanReAgeParameter;
|
||||
}
|
||||
|
||||
// TODO missing hashCode(), equals(Object obj), but probably OK as long as
|
||||
// this is never stored in a Collection.
|
||||
}
|
||||
|
||||
+10
-3
@@ -22,12 +22,15 @@ import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
import jakarta.persistence.Enumerated;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_loan_reage_parameter")
|
||||
@@ -35,9 +38,9 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
@Getter
|
||||
public class LoanReAgeParameter extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
// intentionally not doing a JPA relationship since it's not necessary
|
||||
@Column(name = "loan_transaction_id", nullable = false)
|
||||
private Long loanTransactionId;
|
||||
@OneToOne
|
||||
@JoinColumn(name = "loan_transaction_id", nullable = false)
|
||||
private LoanTransaction loanTransaction;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "frequency_type", nullable = false)
|
||||
@@ -54,4 +57,8 @@ public class LoanReAgeParameter extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
// for JPA, don't use
|
||||
protected LoanReAgeParameter() {}
|
||||
|
||||
public LoanReAgeParameter getCopy(LoanTransaction loanTransaction) {
|
||||
return new LoanReAgeParameter(loanTransaction, frequencyType, frequencyNumber, startDate, numberOfInstallments);
|
||||
}
|
||||
}
|
||||
|
||||
+23
-15
@@ -1293,12 +1293,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
MonetaryCurrency currency = ctx.getCurrency();
|
||||
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||
// Either we have the transaction id or we need to fetch it from context
|
||||
Long loanTransactionId = loanTransaction.getId() != null ? loanTransaction.getId()
|
||||
: ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(loanTransaction);
|
||||
LoanReAgeParameter reAgeParameter = reAgingParameterRepository.findByLoanTransactionId(loanTransactionId).orElseThrow();
|
||||
|
||||
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
|
||||
installments.forEach(i -> {
|
||||
Money principalOutstanding = i.getPrincipalOutstanding(currency);
|
||||
@@ -1308,29 +1306,39 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
});
|
||||
|
||||
Money calculatedPrincipal = outstandingPrincipalBalance.get().dividedBy(reAgeParameter.getNumberOfInstallments(),
|
||||
MoneyHelper.getRoundingMode());
|
||||
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
|
||||
loanTransaction.updateComponentsAndTotal(outstandingPrincipalBalance.get(), Money.zero(currency), Money.zero(currency),
|
||||
Money.zero(currency));
|
||||
|
||||
Money calculatedPrincipal = Money.zero(currency);
|
||||
Money adjustCalculatedPrincipal = Money.zero(currency);
|
||||
if (outstandingPrincipalBalance.get().isGreaterThanZero()) {
|
||||
calculatedPrincipal = outstandingPrincipalBalance.get()
|
||||
.dividedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(), MoneyHelper.getRoundingMode());
|
||||
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
|
||||
}
|
||||
adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
|
||||
.minus(calculatedPrincipal.multipliedBy(loanTransaction.getLoanReAgeParameter().getNumberOfInstallments()));
|
||||
}
|
||||
Money adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
|
||||
.minus(calculatedPrincipal.multipliedBy(reAgeParameter.getNumberOfInstallments()));
|
||||
LoanRepaymentScheduleInstallment lastNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
|
||||
.reduce((first, second) -> second).orElseThrow();
|
||||
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
|
||||
lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1, lastNormalInstallment.getDueDate(),
|
||||
reAgeParameter.getStartDate(), calculatedPrincipal.getAmount());
|
||||
loanTransaction.getLoanReAgeParameter().getStartDate(), calculatedPrincipal.getAmount());
|
||||
installments.add(reAgedInstallment);
|
||||
for (int i = 1; i < reAgeParameter.getNumberOfInstallments(); i++) {
|
||||
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(reAgeParameter, reAgedInstallment.getDueDate());
|
||||
reAgedInstallment.updateDerivedFields(currency, loanTransaction.getTransactionDate());
|
||||
|
||||
for (int i = 1; i < loanTransaction.getLoanReAgeParameter().getNumberOfInstallments(); i++) {
|
||||
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(loanTransaction.getLoanReAgeParameter(),
|
||||
reAgedInstallment.getDueDate());
|
||||
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
|
||||
reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(), calculatedDueDate,
|
||||
calculatedPrincipal.getAmount());
|
||||
installments.add(reAgedInstallment);
|
||||
reAgedInstallment.updateDerivedFields(currency, loanTransaction.getTransactionDate());
|
||||
}
|
||||
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
|
||||
|
||||
reprocessInstallmentsOrder(installments);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user