FINERACT-2107: Fix transaction order
This commit is contained in:
committed by
Arnold Gálovics
parent
15e013b90e
commit
3e64cc1536
+2
-2
@@ -179,11 +179,11 @@ public class ProgressiveLoanInterestScheduleModel {
|
|||||||
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
|
previousInterestPeriod.addDisbursementAmount(disbursedAmount);
|
||||||
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
|
previousInterestPeriod.addBalanceCorrectionAmount(correctionAmount);
|
||||||
final InterestPeriod interestPeriod = new InterestPeriod(repaymentPeriod, previousInterestPeriod.getDueDate(), originalDueDate,
|
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);
|
repaymentPeriod.getInterestPeriods().add(interestPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money getZero(MathContext mc) {
|
private Money getZero() {
|
||||||
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
return Money.zero(loanProductRelatedDetail.getCurrency(), mc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-16
@@ -145,8 +145,10 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
public PayableDetails getPayableDetails(final ProgressiveLoanInterestScheduleModel scheduleModel,
|
||||||
final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) {
|
final LocalDate repaymentPeriodDueDate, final LocalDate targetDate) {
|
||||||
MathContext mc = scheduleModel.mc();
|
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();
|
.filter(rp -> rp.getDueDate().equals(repaymentPeriodDueDate)).findFirst().orElseThrow();
|
||||||
|
|
||||||
LocalDate adjustedTargetDate = targetDate;
|
LocalDate adjustedTargetDate = targetDate;
|
||||||
InterestPeriod interestPeriod;
|
InterestPeriod interestPeriod;
|
||||||
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
if (!targetDate.isAfter(repaymentPeriod.getFromDate())) {
|
||||||
@@ -162,22 +164,12 @@ public final class ProgressiveEMICalculator implements EMICalculator {
|
|||||||
interestPeriod.setDueDate(adjustedTargetDate);
|
interestPeriod.setDueDate(adjustedTargetDate);
|
||||||
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
|
int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod);
|
||||||
repaymentPeriod.getInterestPeriods().subList(index + 1, repaymentPeriod.getInterestPeriods().size()).clear();
|
repaymentPeriod.getInterestPeriods().subList(index + 1, repaymentPeriod.getInterestPeriods().size()).clear();
|
||||||
calculateRateFactorForRepaymentPeriod(repaymentPeriod, scheduleModel);
|
scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate)));
|
||||||
|
calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy);
|
||||||
|
calculateOutstandingBalance(scheduleModelCopy);
|
||||||
|
calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy);
|
||||||
|
|
||||||
// TODO: gather all the unrecognized interest from previous periods based on target date
|
return new PayableDetails(repaymentPeriod.getEmi(), repaymentPeriod.getDuePrincipal(), repaymentPeriod.getDueInterest(),
|
||||||
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,
|
|
||||||
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
|
interestPeriod.getOutstandingLoanBalance().add(interestPeriod.getDisbursementAmount(), mc));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -860,7 +860,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
transactionDate, txnExternalId);
|
transactionDate, txnExternalId);
|
||||||
|
|
||||||
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
|
final boolean isTransactionChronologicallyLatest = loan.isChronologicallyLatestRepaymentOrWaiver(refundTransaction);
|
||||||
loan.getLoanTransactions().add(refundTransaction);
|
|
||||||
|
|
||||||
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
|
boolean shouldCreateInterestRefundTransaction = loan.getLoanProductRelatedDetail().getSupportedInterestRefundTypes().stream()
|
||||||
.map(LoanSupportedInterestRefundTypes::getTransactionType)
|
.map(LoanSupportedInterestRefundTypes::getTransactionType)
|
||||||
@@ -869,9 +868,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
|
|
||||||
if (shouldCreateInterestRefundTransaction) {
|
if (shouldCreateInterestRefundTransaction) {
|
||||||
interestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
|
interestRefundTransaction = createInterestRefundLoanTransaction(loan, transactionDate, transactionAmount);
|
||||||
if (interestRefundTransaction != null) {
|
|
||||||
loan.addLoanTransaction(interestRefundTransaction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
|
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = transactionProcessorFactory
|
||||||
@@ -887,13 +883,24 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
|||||||
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); //
|
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(refundTransaction.getAmount(loan.getCurrency())); //
|
||||||
|
|
||||||
if (!reprocess) {
|
if (!reprocess) {
|
||||||
|
loan.getLoanTransactions().add(refundTransaction);
|
||||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction,
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(refundTransaction,
|
||||||
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));
|
||||||
|
if (interestRefundTransaction != null) {
|
||||||
|
loan.addLoanTransaction(interestRefundTransaction);
|
||||||
|
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(interestRefundTransaction,
|
||||||
|
new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(),
|
||||||
|
new MoneyHolder(loan.getTotalOverpaidAsMoney()), null));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
if (loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||||
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
loan.regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||||
}
|
}
|
||||||
|
loan.getLoanTransactions().add(refundTransaction);
|
||||||
|
if (interestRefundTransaction != null) {
|
||||||
|
loan.addLoanTransaction(interestRefundTransaction);
|
||||||
|
}
|
||||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = loan.retrieveListOfTransactionsForReprocessing();
|
||||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||||
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
|
loan.getDisbursementDate(), allNonContraTransactionsPostDisbursement, loan.getCurrency(),
|
||||||
|
|||||||
+1
-1
@@ -183,7 +183,7 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest {
|
|||||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", "22 January 2021", 5.75,
|
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);
|
enableLoanInterestRefundPstBusinessEvent(false);
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-3
@@ -18,6 +18,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.REPLAYED;
|
||||||
|
|
||||||
import io.restassured.builder.RequestSpecBuilder;
|
import io.restassured.builder.RequestSpecBuilder;
|
||||||
import io.restassured.builder.ResponseSpecBuilder;
|
import io.restassured.builder.ResponseSpecBuilder;
|
||||||
import io.restassured.http.ContentType;
|
import io.restassured.http.ContentType;
|
||||||
@@ -28,6 +30,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
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.PostClientsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||||
@@ -142,12 +145,39 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest {
|
|||||||
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId());
|
||||||
|
|
||||||
logTransactions(loanId);
|
logTransactions(loanId);
|
||||||
verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"),
|
verifyTransactions(loanId, //
|
||||||
transaction(1000.0, "Payout Refund", "22 January 2021"), transaction(5.75, "Accrual", "22 January 2021"),
|
transaction(1000.0, "Disbursement", "01 January 2021"), //
|
||||||
transaction(5.75, "Interest Refund", "22 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
|
@Test
|
||||||
public void verifyInterestRefundCreatedForMerchantIssuedRefund() {
|
public void verifyInterestRefundCreatedForMerchantIssuedRefund() {
|
||||||
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||||
|
|||||||
Reference in New Issue
Block a user