Skip to content

Commit

Permalink
FINERACT-2107: Fix transaction order
Browse files Browse the repository at this point in the history
  • Loading branch information
adamsaghy committed Oct 28, 2024
1 parent 7b05432 commit fdc0f38
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -179,11 +179,11 @@ void insertInterestPeriod(final RepaymentPeriod repaymentPeriod, final LocalDate
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
BigDecimal.ZERO, getZero(mc), getZero(mc), getZero(mc), mc);
BigDecimal.ZERO, getZero(), getZero(), getZero(), mc);
repaymentPeriod.getInterestPeriods().add(interestPeriod);
}

private Money getZero(MathContext mc) {
private Money getZero() {
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,10 @@ public void payPrincipal(ProgressiveLoanInterestScheduleModel scheduleModel, Loc
public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel,
final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) {
MathContext mc = scheduleModel.mc();
RepaymentPeriod repaymentPeriod = scheduleModel.deepCopy(mc).repaymentPeriods().stream()
ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc);
RepaymentPeriod repaymentPeriod = scheduleModelCopy.repaymentPeriods().stream()
.filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow();

LocalDate adjustedTargetDate = targetDate;
InterestPeriod interestPeriod;
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
Expand All @@ -162,22 +164,12 @@ public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleMod
interestPeriod.setDueDate(adjustedTargetDate);
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
repaymentPeriod.getInterestPeriods().subList(index + 1, repaymentPeriod.getInterestPeriods().size()).clear();
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);

// TODO: gather all the unrecognized interest from previous periods based on target date
Money payableInterest = targetDate.isBefore(repaymentPeriod.getFromDate())
? Money.zero(scheduleModel.loanProductRelatedDetail().getCurrency(), mc)
: repaymentPeriod.getDueInterest();
Money outstandingLoanBalance = interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount());

Money calculatedEmi = outstandingLoanBalance.plus(payableInterest, mc);
if (calculatedEmi.isLessThan(repaymentPeriod.getEmi())) {
// Review this logic
repaymentPeriod.setEmi(outstandingLoanBalance.plus(payableInterest).plus(repaymentPeriod.getPaidInterest(), mc)
.plus(repaymentPeriod.getPaidPrincipal(), mc));
}
Money payablePrincipal = repaymentPeriod.getEmi().minus(payableInterest, mc);
return new PayableDetails(repaymentPeriod.getEmi(), payablePrincipal, payableInterest,
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
calculateOutstandingBalance(scheduleModelCopy);
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);

return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(),
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,6 @@ public Pair<LoanTransaction, LoanTransaction> makeRefund(final Loan loan, final
transactionDate, txnExternalId);

final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
loan.getLoanTransactions().add(refundTransaction);

boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
.map(LoanSupportedInterestRefundTypes::getTransactionType)
Expand All @@ -869,9 +868,6 @@ public Pair<LoanTransaction, LoanTransaction> makeRefund(final Loan loan, final

if (shouldCreateInterestRefundTransaction) {
interestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
if (interestRefundTransaction != null) {
loan.addLoanTransaction(interestRefundTransaction);
}
}

final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
Expand All @@ -887,13 +883,24 @@ public Pair<LoanTransaction, LoanTransaction> makeRefund(final Loan loan, final
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); //

if (!reprocess) {
loan.getLoanTransactions().add(refundTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
if (interestRefundTransaction != null) {
loan.addLoanTransaction(interestRefundTransaction);
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(interestRefundTransaction,
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
}
} else {
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
loan.getLoanTransactions().add(refundTransaction);
if (interestRefundTransaction != null) {
loan.addLoanTransaction(interestRefundTransaction);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public void verifyInterestRefundPostBusinessEventCreatedForMerchantIssuedRefundW
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());

verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", "22 January 2021", 5.75,
994.25, 5.75, 0.0, 0.0, 0.0));
0.0, 5.75, 0.0, 0.0, 0.0));
});
enableLoanInterestRefundPstBusinessEvent(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
*/
package org.apache.fineract.integrationtests;

import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.REPLAYED;

import io.restassured.builder.RequestSpecBuilder;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.http.ContentType;
Expand All @@ -28,6 +30,7 @@
import java.util.concurrent.atomic.AtomicReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.PostClientsResponse;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
Expand Down Expand Up @@ -142,12 +145,39 @@ public void verifyInterestRefundCreatedForPayoutRefund() {
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());

logTransactions(loanId);
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"),
transaction(5.75, "Interest Refund", "22 January 2021"));
verifyTransactions(loanId, //
transaction(1000.0, "Disbursement", "01 January 2021"), //
transaction(1000.0, "Payout Refund", "22 January 2021"), //
transaction(5.75, "Interest Refund", "22 January 2021"), //
transaction(5.75, "Accrual", "22 January 2021")); //

checkTransactionWasNotReverseReplayed(postLoansLoanIdTransactionsResponse.getLoanId(),
postLoansLoanIdTransactionsResponse.getResourceId());
checkTransactionWasNotReverseReplayed(postLoansLoanIdTransactionsResponse.getLoanId(),
postLoansLoanIdTransactionsResponse.getSubResourceId());

verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getResourceId(), journalEntry(1000, fundSource, "DEBIT"), //
journalEntry(5.75, interestReceivableAccount, "CREDIT"), //
journalEntry(994.25, loansReceivableAccount, "CREDIT"));

verifyTRJournalEntries(postLoansLoanIdTransactionsResponse.getSubResourceId(),
journalEntry(5.75, interestIncomeAccount, "DEBIT"), //
journalEntry(5.75, loansReceivableAccount, "CREDIT")); //
});
}

private void checkTransactionWasNotReverseReplayed(Long loanId, Long transactionId) {
GetLoansLoanIdTransactionsTransactionIdResponse loanTransactionDetails = loanTransactionHelper.getLoanTransactionDetails(loanId,
transactionId);
if (loanTransactionDetails.getTransactionRelations() != null) {
loanTransactionDetails.getTransactionRelations().forEach(transactionRelation -> {
if (REPLAYED.name().equals(transactionRelation.getRelationType())) {
Assertions.fail("Transaction was replayed!");
}
});
}
}

@Test
public void verifyInterestRefundCreatedForMerchantIssuedRefund() {
AtomicReference<Long> loanIdRef = new AtomicReference<>();
Expand Down

0 comments on commit fdc0f38

Please sign in to comment.