FINERACT-1806: Reverse-replay logic for Charge-off transaction
This commit is contained in:
+57
-129
@@ -59,9 +59,7 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||||
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
|
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
|
||||||
@@ -3368,7 +3366,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||||
List<LoanTransaction> trans = getLoanTransactions();
|
List<LoanTransaction> trans = getLoanTransactions();
|
||||||
for (final LoanTransaction transaction : trans) {
|
for (final LoanTransaction transaction : trans) {
|
||||||
if (transaction.isNotReversed() && !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction())) {
|
if (transaction.isNotReversed()
|
||||||
|
&& (transaction.isChargeOff() || !(transaction.isDisbursement() || transaction.isNonMonetaryTransaction()))) {
|
||||||
repaymentsOrWaivers.add(transaction);
|
repaymentsOrWaivers.add(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4486,20 +4485,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
|
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, boolean isAccountTransfer) {
|
||||||
|
|
||||||
final List<Map<String, Object>> accountingBridgeData = new ArrayList<>();
|
final List<Map<String, Object>> accountingBridgeData = new ArrayList<>();
|
||||||
|
|
||||||
// get map before charge-off
|
|
||||||
final List<Map<String, Object>> newLoanTransactionsBeforeChargeOff = new ArrayList<>();
|
final List<Map<String, Object>> newLoanTransactionsBeforeChargeOff = new ArrayList<>();
|
||||||
final Map<String, Object> accountingBridgeDataBeforeChargeOff = getAccountingMapForChargeOffDateCriteria(currencyCode,
|
|
||||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, newLoanTransactionsBeforeChargeOff, true);
|
|
||||||
|
|
||||||
// get map after charge-off
|
|
||||||
final List<Map<String, Object>> newLoanTransactionsAfterChargeOff = new ArrayList<>();
|
final List<Map<String, Object>> newLoanTransactionsAfterChargeOff = new ArrayList<>();
|
||||||
final Map<String, Object> accountingBridgeDataAfterChargeOff = getAccountingMapForChargeOffDateCriteria(currencyCode,
|
// get map before charge-off
|
||||||
existingTransactionIds, existingReversedTransactionIds, isAccountTransfer, newLoanTransactionsAfterChargeOff, false);
|
final Map<String, Object> accountingBridgeDataBeforeChargeOff = buildAccountingMapForChargeOffDateCriteria(currencyCode,
|
||||||
|
isAccountTransfer, true);
|
||||||
|
// get map after charge-off
|
||||||
|
final Map<String, Object> accountingBridgeDataAfterChargeOff = buildAccountingMapForChargeOffDateCriteria(currencyCode,
|
||||||
|
isAccountTransfer, false);
|
||||||
|
|
||||||
// get map onCharge off date
|
// split the transactions according charge-off date
|
||||||
getAccountingMapDataOnChargeOffDate(currencyCode, existingTransactionIds, existingReversedTransactionIds,
|
classifyTransactionsBasedOnChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff,
|
||||||
newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff);
|
existingTransactionIds, existingReversedTransactionIds, currencyCode);
|
||||||
|
|
||||||
accountingBridgeDataBeforeChargeOff.put("newLoanTransactions", newLoanTransactionsBeforeChargeOff);
|
accountingBridgeDataBeforeChargeOff.put("newLoanTransactions", newLoanTransactionsBeforeChargeOff);
|
||||||
accountingBridgeData.add(accountingBridgeDataBeforeChargeOff);
|
accountingBridgeData.add(accountingBridgeDataBeforeChargeOff);
|
||||||
@@ -4510,6 +4507,21 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return accountingBridgeData;
|
return accountingBridgeData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void classifyTransactionsBasedOnChargeOffDate(List<Map<String, Object>> newLoanTransactionsBeforeChargeOff,
|
||||||
|
List<Map<String, Object>> newLoanTransactionsAfterChargeOff, List<Long> existingTransactionIds,
|
||||||
|
List<Long> existingReversedTransactionIds, String currencyCode) {
|
||||||
|
// Before
|
||||||
|
filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, currencyCode, existingTransactionIds,
|
||||||
|
existingReversedTransactionIds, transaction -> transaction.getTransactionDate().isBefore(getChargedOffOnDate()));
|
||||||
|
// On
|
||||||
|
filterTransactionsByChargeOffDate(newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, currencyCode,
|
||||||
|
existingTransactionIds, existingReversedTransactionIds,
|
||||||
|
transaction -> transaction.getTransactionDate().isEqual(getChargedOffOnDate()));
|
||||||
|
// After
|
||||||
|
filterTransactionsByChargeOffDate(newLoanTransactionsAfterChargeOff, currencyCode, existingTransactionIds,
|
||||||
|
existingReversedTransactionIds, transaction -> transaction.getTransactionDate().isAfter(getChargedOffOnDate()));
|
||||||
|
}
|
||||||
|
|
||||||
private Map<String, Object> getAccountingBridgeDataGenericAttributes(final String currencyCode, boolean isAccountTransfer) {
|
private Map<String, Object> getAccountingBridgeDataGenericAttributes(final String currencyCode, boolean isAccountTransfer) {
|
||||||
final Map<String, Object> accountingBridgeDataGenericAttributes = new LinkedHashMap<>();
|
final Map<String, Object> accountingBridgeDataGenericAttributes = new LinkedHashMap<>();
|
||||||
accountingBridgeDataGenericAttributes.put("loanId", getId());
|
accountingBridgeDataGenericAttributes.put("loanId", getId());
|
||||||
@@ -4525,8 +4537,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
return accountingBridgeDataGenericAttributes;
|
return accountingBridgeDataGenericAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, Object> getAccountingMapForChargeOffDateCriteria(final String currencyCode, final List<Long> existingTransactionIds,
|
private Map<String, Object> buildAccountingMapForChargeOffDateCriteria(final String currencyCode, boolean isAccountTransfer,
|
||||||
final List<Long> existingReversedTransactionIds, boolean isAccountTransfer, List<Map<String, Object>> newLoanTransactions,
|
|
||||||
boolean isBeforeChargeOffDate) {
|
boolean isBeforeChargeOffDate) {
|
||||||
final Map<String, Object> accountingBridgeDataChargeOff = new LinkedHashMap<>(
|
final Map<String, Object> accountingBridgeDataChargeOff = new LinkedHashMap<>(
|
||||||
getAccountingBridgeDataGenericAttributes(currencyCode, isAccountTransfer));
|
getAccountingBridgeDataGenericAttributes(currencyCode, isAccountTransfer));
|
||||||
@@ -4537,124 +4548,41 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
|||||||
accountingBridgeDataChargeOff.put("isChargeOff", isChargedOff());
|
accountingBridgeDataChargeOff.put("isChargeOff", isChargedOff());
|
||||||
accountingBridgeDataChargeOff.put("isFraud", isFraud());
|
accountingBridgeDataChargeOff.put("isFraud", isFraud());
|
||||||
}
|
}
|
||||||
Predicate<LoanTransaction> chargeOffDateCriteria = isBeforeChargeOffDate
|
|
||||||
? transaction -> transaction.getTransactionDate().isBefore(getChargedOffOnDate())
|
|
||||||
: transaction -> transaction.getTransactionDate().isAfter(getChargedOffOnDate());
|
|
||||||
getTransactionsForAccountingBridgeData(currencyCode, existingTransactionIds, existingReversedTransactionIds, newLoanTransactions,
|
|
||||||
chargeOffDateCriteria);
|
|
||||||
return accountingBridgeDataChargeOff;
|
return accountingBridgeDataChargeOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getAccountingMapDataOnChargeOffDate(String currencyCode, List<Long> existingTransactionIds,
|
private void filterTransactionsByChargeOffDate(List<Map<String, Object>> filteredTransactions, final String currencyCode,
|
||||||
List<Long> existingReversedTransactionIds, List<Map<String, Object>> newLoanTransactionsBeforeChargeOff,
|
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||||
List<Map<String, Object>> newLoanTransactionsAfterChargeOff) {
|
|
||||||
Predicate<LoanTransaction> isOnChargeOff = transaction -> transaction.getTransactionDate().isEqual(getChargedOffOnDate());
|
|
||||||
List<LoanTransaction> transactionsOnChargeOffDate = this.loanTransactions.stream().filter(isOnChargeOff)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* TODO: Modify logic to retrieve correct charge-off transaction once reverse replay of charge-off is
|
|
||||||
* implemented
|
|
||||||
*/
|
|
||||||
LoanTransaction chargeOffTransaction = this.loanTransactions.stream().filter(LoanTransaction::isChargeOff).findFirst().get();
|
|
||||||
for (final LoanTransaction transaction : transactionsOnChargeOffDate) {
|
|
||||||
checkAndAddReversedTransactionOnChargeOffDate(currencyCode, transaction, existingTransactionIds, existingReversedTransactionIds,
|
|
||||||
newLoanTransactionsBeforeChargeOff, newLoanTransactionsAfterChargeOff, chargeOffTransaction);
|
|
||||||
checkAndAddNewTransactionOnChargeOffDate(currencyCode, transaction, existingTransactionIds, newLoanTransactionsBeforeChargeOff,
|
|
||||||
newLoanTransactionsAfterChargeOff, chargeOffTransaction);
|
|
||||||
checkAndAddChargeOffTransaction(currencyCode, transaction, existingTransactionIds, newLoanTransactionsAfterChargeOff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAndAddReversedTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction,
|
|
||||||
List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds,
|
|
||||||
List<Map<String, Object>> newLoanTransactionsBeforeChargeOff, List<Map<String, Object>> newLoanTransactionsAfterChargeOff,
|
|
||||||
LoanTransaction chargeOffTransaction) {
|
|
||||||
if (!transaction.isChargeOff()) {
|
|
||||||
if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId())
|
|
||||||
&& !existingReversedTransactionIds.contains(transaction.getId())) {
|
|
||||||
compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff,
|
|
||||||
newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId());
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAndAddReverseReplayedTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction,
|
|
||||||
List<Map<String, Object>> newLoanTransactionsBeforeChargeOff, List<Map<String, Object>> newLoanTransactionsAfterChargeOff,
|
|
||||||
LoanTransaction chargeOffTransaction) {
|
|
||||||
Predicate<LoanTransactionRelation> isReplayed = transactionRelation -> LoanTransactionRelationTypeEnum.REPLAYED
|
|
||||||
.equals(transactionRelation.getRelationType());
|
|
||||||
List<LoanTransactionRelation> replayedTransactionRelations = transaction.getLoanTransactionRelations().stream().filter(isReplayed)
|
|
||||||
.sorted(Comparator.comparing(LoanTransactionRelation::getToTransaction, Comparator.comparingLong(LoanTransaction::getId)))
|
|
||||||
.toList();
|
|
||||||
if (!replayedTransactionRelations.isEmpty()) {
|
|
||||||
// Transaction Id for first reversed transaction
|
|
||||||
Long transactionIdForReversedTransaction = replayedTransactionRelations.get(0).getToTransaction().getId();
|
|
||||||
if (transactionIdForReversedTransaction > chargeOffTransaction.getId()) {
|
|
||||||
newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode));
|
|
||||||
} else {
|
|
||||||
newLoanTransactionsBeforeChargeOff.add(transaction.toMapData(currencyCode));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// new transaction
|
|
||||||
compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff,
|
|
||||||
newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAndAddNewTransactionOnChargeOffDate(String currencyCode, LoanTransaction transaction,
|
|
||||||
List<Long> existingTransactionIds, List<Map<String, Object>> newLoanTransactionsBeforeChargeOff,
|
|
||||||
List<Map<String, Object>> newLoanTransactionsAfterChargeOff, LoanTransaction chargeOffTransaction) {
|
|
||||||
if (!transaction.isChargeOff()) {
|
|
||||||
if (!existingTransactionIds.contains(transaction.getId())) {
|
|
||||||
if (!transaction.getLoanTransactionRelations().isEmpty()) {
|
|
||||||
checkAndAddReverseReplayedTransactionOnChargeOffDate(currencyCode, transaction, newLoanTransactionsBeforeChargeOff,
|
|
||||||
newLoanTransactionsAfterChargeOff, chargeOffTransaction);
|
|
||||||
} else {
|
|
||||||
// new transaction
|
|
||||||
compareWithChargeOffIdAndAddTransactionForAccountingData(currencyCode, newLoanTransactionsBeforeChargeOff,
|
|
||||||
newLoanTransactionsAfterChargeOff, transaction, chargeOffTransaction.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAndAddChargeOffTransaction(String currencyCode, LoanTransaction transaction, List<Long> existingTransactionIds,
|
|
||||||
List<Map<String, Object>> newLoanTransactionsAfterChargeOff) {
|
|
||||||
if (transaction.isChargeOff()) {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* TODO: Modify logic for reverse replay of charge-off
|
|
||||||
*/
|
|
||||||
if (!existingTransactionIds.contains(transaction.getId())) {
|
|
||||||
newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void compareWithChargeOffIdAndAddTransactionForAccountingData(final String currencyCode,
|
|
||||||
List<Map<String, Object>> newLoanTransactionsBeforeChargeOff, List<Map<String, Object>> newLoanTransactionsAfterChargeOff,
|
|
||||||
LoanTransaction transaction, Long chargeOffTransactionId) {
|
|
||||||
if (transaction.getId() > chargeOffTransactionId) {
|
|
||||||
newLoanTransactionsAfterChargeOff.add(transaction.toMapData(currencyCode));
|
|
||||||
} else {
|
|
||||||
newLoanTransactionsBeforeChargeOff.add(transaction.toMapData(currencyCode));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getTransactionsForAccountingBridgeData(final String currencyCode, final List<Long> existingTransactionIds,
|
|
||||||
final List<Long> existingReversedTransactionIds, final List<Map<String, Object>> newLoanTransactions,
|
|
||||||
Predicate<LoanTransaction> chargeOffDateCriteria) {
|
Predicate<LoanTransaction> chargeOffDateCriteria) {
|
||||||
Consumer<LoanTransaction> addTransactionForAccounting = transaction -> {
|
filteredTransactions.addAll(this.loanTransactions.stream().filter(chargeOffDateCriteria).filter(transaction -> {
|
||||||
if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId())
|
boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId());
|
||||||
&& !existingReversedTransactionIds.contains(transaction.getId())) {
|
boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId());
|
||||||
newLoanTransactions.add(transaction.toMapData(currencyCode));
|
|
||||||
} else if (!existingTransactionIds.contains(transaction.getId())) {
|
if (transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction) {
|
||||||
newLoanTransactions.add(transaction.toMapData(currencyCode));
|
return true;
|
||||||
|
} else {
|
||||||
|
return !isExistingTransaction;
|
||||||
}
|
}
|
||||||
};
|
}).map(transaction -> transaction.toMapData(currencyCode)).toList());
|
||||||
this.loanTransactions.stream().filter(chargeOffDateCriteria).forEach(addTransactionForAccounting);
|
}
|
||||||
|
|
||||||
|
private void filterTransactionsByChargeOffDate(List<Map<String, Object>> newLoanTransactionsBeforeChargeOff,
|
||||||
|
List<Map<String, Object>> newLoanTransactionsAfterChargeOff, String currencyCode, List<Long> existingTransactionIds,
|
||||||
|
List<Long> existingReversedTransactionIds, Predicate<LoanTransaction> chargeOffDateCriteria) {
|
||||||
|
|
||||||
|
LoanTransaction chargeOffTransaction = this.loanTransactions.stream().filter(LoanTransaction::isChargeOff)
|
||||||
|
.filter(LoanTransaction::isNotReversed).findFirst().get();
|
||||||
|
|
||||||
|
this.loanTransactions.stream().filter(chargeOffDateCriteria).forEach(transaction -> {
|
||||||
|
boolean isExistingTransaction = existingTransactionIds.contains(transaction.getId());
|
||||||
|
boolean isExistingReversedTransaction = existingReversedTransactionIds.contains(transaction.getId());
|
||||||
|
List<Map<String, Object>> targetList = (transaction.getId().compareTo(chargeOffTransaction.getId()) < 0)
|
||||||
|
? newLoanTransactionsBeforeChargeOff
|
||||||
|
: newLoanTransactionsAfterChargeOff;
|
||||||
|
if ((transaction.isReversed() && isExistingTransaction && !isExistingReversedTransaction) || !isExistingTransaction) {
|
||||||
|
targetList.add(transaction.toMapData(currencyCode));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> deriveAccountingBridgeData(final String currencyCode, final List<Long> existingTransactionIds,
|
public Map<String, Object> deriveAccountingBridgeData(final String currencyCode, final List<Long> existingTransactionIds,
|
||||||
|
|||||||
+40
-13
@@ -189,7 +189,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
||||||
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
||||||
} else {
|
} else {
|
||||||
createNewTransactionIfNecessary(loanTransaction, newLoanTransaction, currency, changedTransactionDetail);
|
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +204,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
} else if (loanTransaction.isChargeback()) {
|
} else if (loanTransaction.isChargeback()) {
|
||||||
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed);
|
recalculateCreditTransaction(changedTransactionDetail, loanTransaction, currency, installments, transactionsToBeProcessed);
|
||||||
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
||||||
|
} else if (loanTransaction.isChargeOff()) {
|
||||||
|
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reprocessInstallments(installments, currency);
|
reprocessInstallments(installments, currency);
|
||||||
@@ -211,6 +213,30 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
|
||||||
|
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||||
|
newLoanTransaction.resetDerivedComponents();
|
||||||
|
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||||
|
Money principalPortion = Money.zero(currency);
|
||||||
|
Money interestPortion = Money.zero(currency);
|
||||||
|
Money feeChargesPortion = Money.zero(currency);
|
||||||
|
Money penaltychargesPortion = Money.zero(currency);
|
||||||
|
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||||
|
if (currentInstallment.isNotFullyPaidOff()) {
|
||||||
|
principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency));
|
||||||
|
interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency));
|
||||||
|
feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency));
|
||||||
|
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newLoanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
|
||||||
|
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||||
|
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
|
private void reprocessChargebackTransactionRelation(ChangedTransactionDetail changedTransactionDetail,
|
||||||
List<LoanTransaction> transactionsToBeProcessed) {
|
List<LoanTransaction> transactionsToBeProcessed) {
|
||||||
|
|
||||||
@@ -260,7 +286,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
List<LoanTransaction> mergedList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
List<LoanTransaction> mergedList = getMergedTransactionList(transactionsToBeProcessed, changedTransactionDetail);
|
||||||
Money overpaidAmount = calculateOverpaidAmount(loanTransaction, mergedList, installments, currency);
|
Money overpaidAmount = calculateOverpaidAmount(loanTransaction, mergedList, installments, currency);
|
||||||
processCreditTransaction(newLoanTransaction, overpaidAmount, currency, installments);
|
processCreditTransaction(newLoanTransaction, overpaidAmount, currency, installments);
|
||||||
createNewTransactionIfNecessary(loanTransaction, newLoanTransaction, currency, changedTransactionDetail);
|
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||||
|
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
|
private List<LoanTransaction> getMergedTransactionList(List<LoanTransaction> transactionList,
|
||||||
@@ -270,17 +298,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
return mergedList;
|
return mergedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createNewTransactionIfNecessary(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
private void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||||
MonetaryCurrency currency, ChangedTransactionDetail changedTransactionDetail) {
|
ChangedTransactionDetail changedTransactionDetail) {
|
||||||
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
loanTransaction.reverse();
|
||||||
loanTransaction.reverse();
|
loanTransaction.updateExternalId(null);
|
||||||
loanTransaction.updateExternalId(null);
|
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||||
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||||
// Adding Replayed relation from newly created transaction to reversed transaction
|
newLoanTransaction.getLoanTransactionRelations().add(
|
||||||
newLoanTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(newLoanTransaction,
|
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||||
loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
|
||||||
changedTransactionDetail.getNewTransactionMappings().put(loanTransaction.getId(), newLoanTransaction);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Money calculateOverpaidAmount(LoanTransaction loanTransaction, List<LoanTransaction> transactions,
|
private Money calculateOverpaidAmount(LoanTransaction loanTransaction, List<LoanTransaction> transactions,
|
||||||
|
|||||||
+16
-24
@@ -58,15 +58,20 @@ import org.apache.fineract.integrationtests.common.funds.FundsHelper;
|
|||||||
import org.apache.fineract.integrationtests.common.funds.FundsResourceHandler;
|
import org.apache.fineract.integrationtests.common.funds.FundsResourceHandler;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductHelper;
|
import org.apache.fineract.integrationtests.common.loans.LoanProductHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||||
import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
import org.apache.fineract.integrationtests.common.system.CodeHelper;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Disabled;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
|
||||||
|
@ExtendWith(LoanTestLifecycleExtension.class)
|
||||||
public class LoanPostChargeOffScenariosTest {
|
public class LoanPostChargeOffScenariosTest {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter DATE_FORMATTER = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
|
||||||
private ResponseSpecification responseSpec;
|
private ResponseSpecification responseSpec;
|
||||||
private RequestSpecification requestSpec;
|
private RequestSpecification requestSpec;
|
||||||
private ClientHelper clientHelper;
|
private ClientHelper clientHelper;
|
||||||
@@ -77,33 +82,23 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
// asset
|
// asset
|
||||||
private Account loansReceivable;
|
private Account loansReceivable;
|
||||||
private Account interestFeeReceivable;
|
private Account interestFeeReceivable;
|
||||||
private Account otherReceivables;
|
|
||||||
private Account uncReceivable;
|
|
||||||
private Account suspenseAccount;
|
private Account suspenseAccount;
|
||||||
private Account fundReceivables;
|
private Account fundReceivables;
|
||||||
|
|
||||||
// liability
|
// liability
|
||||||
private Account aaSuspenseBalance;
|
|
||||||
private Account suspenseClearingAccount;
|
private Account suspenseClearingAccount;
|
||||||
private Account overpaymentAccount;
|
private Account overpaymentAccount;
|
||||||
|
|
||||||
// income
|
// income
|
||||||
private Account deferredInterestRevenue;
|
|
||||||
private Account retainedEarningsPriorYear;
|
|
||||||
private Account interestIncome;
|
private Account interestIncome;
|
||||||
private Account feeIncome;
|
private Account feeIncome;
|
||||||
private Account feeChargeOff;
|
private Account feeChargeOff;
|
||||||
private Account recoveries;
|
private Account recoveries;
|
||||||
private Account interestIncomeChargeOff;
|
private Account interestIncomeChargeOff;
|
||||||
|
|
||||||
// expense
|
// expense
|
||||||
private Account creditLossBadDebt;
|
private Account creditLossBadDebt;
|
||||||
private Account creditLossBadDebtFraud;
|
private Account creditLossBadDebtFraud;
|
||||||
private Account writtenOff;
|
private Account writtenOff;
|
||||||
private Account goodwillExpenseAccount;
|
private Account goodwillExpenseAccount;
|
||||||
|
|
||||||
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() {
|
public void setup() {
|
||||||
Utils.initializeRESTAssured();
|
Utils.initializeRESTAssured();
|
||||||
@@ -117,19 +112,14 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
// Asset
|
// Asset
|
||||||
this.loansReceivable = this.accountHelper.createAssetAccount();
|
this.loansReceivable = this.accountHelper.createAssetAccount();
|
||||||
this.interestFeeReceivable = this.accountHelper.createAssetAccount();
|
this.interestFeeReceivable = this.accountHelper.createAssetAccount();
|
||||||
this.otherReceivables = this.accountHelper.createAssetAccount();
|
|
||||||
this.uncReceivable = this.accountHelper.createAssetAccount();
|
|
||||||
this.suspenseAccount = this.accountHelper.createAssetAccount();
|
this.suspenseAccount = this.accountHelper.createAssetAccount();
|
||||||
this.fundReceivables = this.accountHelper.createAssetAccount();
|
this.fundReceivables = this.accountHelper.createAssetAccount();
|
||||||
|
|
||||||
// Liability
|
// Liability
|
||||||
this.aaSuspenseBalance = this.accountHelper.createLiabilityAccount();
|
|
||||||
this.suspenseClearingAccount = this.accountHelper.createLiabilityAccount();
|
this.suspenseClearingAccount = this.accountHelper.createLiabilityAccount();
|
||||||
this.overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
this.overpaymentAccount = this.accountHelper.createLiabilityAccount();
|
||||||
|
|
||||||
// income
|
// income
|
||||||
this.deferredInterestRevenue = this.accountHelper.createIncomeAccount();
|
|
||||||
this.retainedEarningsPriorYear = this.accountHelper.createIncomeAccount();
|
|
||||||
this.interestIncome = this.accountHelper.createIncomeAccount();
|
this.interestIncome = this.accountHelper.createIncomeAccount();
|
||||||
this.feeIncome = this.accountHelper.createIncomeAccount();
|
this.feeIncome = this.accountHelper.createIncomeAccount();
|
||||||
this.feeChargeOff = this.accountHelper.createIncomeAccount();
|
this.feeChargeOff = this.accountHelper.createIncomeAccount();
|
||||||
@@ -158,7 +148,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -328,7 +318,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -452,7 +442,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -586,7 +576,8 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transactionOnChargeOfDatePreChargeOffReverseReplayTest() {
|
@Disabled("Requires: FINERACT-1946")
|
||||||
|
public void transactionOnChargeOffDatePreChargeOffReverseReplayTest() {
|
||||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting();
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting();
|
||||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||||
@@ -597,7 +588,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -730,7 +721,8 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void transactionOnChargeOfDatePostChargeOffReverseReplayTest() {
|
@Disabled("Requires: FINERACT-1946")
|
||||||
|
public void transactionOnChargeOffDatePostChargeOffReverseReplayTest() {
|
||||||
String loanExternalIdStr = UUID.randomUUID().toString();
|
String loanExternalIdStr = UUID.randomUUID().toString();
|
||||||
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting();
|
final Integer loanProductID = createLoanProductWithPeriodicAccrualAccounting();
|
||||||
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
final Integer clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId().intValue();
|
||||||
@@ -741,7 +733,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -885,7 +877,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
@@ -992,7 +984,7 @@ public class LoanPostChargeOffScenariosTest {
|
|||||||
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, "10", false));
|
||||||
|
|
||||||
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
LocalDate targetDate = LocalDate.of(2022, 9, 5);
|
||||||
final String feeCharge1AddedDate = dateFormatter.format(targetDate);
|
final String feeCharge1AddedDate = DATE_FORMATTER.format(targetDate);
|
||||||
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
Integer feeLoanChargeId = loanTransactionHelper.addChargesForLoan(loanId,
|
||||||
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(feeCharge), feeCharge1AddedDate, "10"));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user