FINERACT-1724: Refactor loan repayment transaction processor

This commit is contained in:
Adam Saghy
2023-08-30 13:48:26 +02:00
parent e2e10d2008
commit 59c5ad4500
12 changed files with 213 additions and 386 deletions
@@ -702,7 +702,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -789,7 +789,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
final Set<LoanCharge> loanCharges = new HashSet<>(1); final Set<LoanCharge> loanCharges = new HashSet<>(1);
loanCharges.add(charge); loanCharges.add(charge);
loanRepaymentScheduleTransactionProcessor.handleTransaction(chargesPayment, getCurrency(), chargePaymentInstallments, loanCharges); loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargesPayment, getCurrency(), chargePaymentInstallments,
loanCharges, getTotalOverpaidAsMoney());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine); doPostLoanTransactionChecks(chargesPayment.getTransactionDate(), loanLifecycleStateMachine);
@@ -862,8 +863,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* affected Transactions * affected Transactions
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
} }
this.charges.remove(loanCharge); this.charges.remove(loanCharge);
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -930,8 +931,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* affected Transactions * affected Transactions
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
@@ -1123,8 +1124,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
* affected Transactions * affected Transactions
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
@@ -2705,7 +2706,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (!allNonContraTransactionsPostDisbursement.isEmpty()) { if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
@@ -3330,8 +3331,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (isTransactionChronologicallyLatest && adjustedTransaction == null if (isTransactionChronologicallyLatest && adjustedTransaction == null
&& (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled()) && !isForeclosure()) { && (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled()) && !isForeclosure()) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), getRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, getCurrency(),
getActiveCharges()); getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
reprocess = false; reprocess = false;
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) { if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
@@ -3350,7 +3351,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
@@ -3778,7 +3779,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges()); getActiveCharges());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -3904,7 +3905,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
addLoanTransaction(loanTransaction); addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), getRepaymentScheduleInstallments()); loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, loanCurrency(),
getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
@@ -3926,7 +3928,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO); regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} }
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
@@ -4008,8 +4010,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
} }
addLoanTransaction(loanTransaction); addLoanTransaction(loanTransaction);
loanRepaymentScheduleTransactionProcessor.handleWriteOff(loanTransaction, loanCurrency(), loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, loanCurrency(),
getRepaymentScheduleInstallments()); getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} else if (totalOutstanding.isGreaterThanZero()) { } else if (totalOutstanding.isGreaterThanZero()) {
@@ -5400,7 +5402,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges()); getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -5478,6 +5480,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
return this.totalOverpaid; return this.totalOverpaid;
} }
public Money getTotalOverpaidAsMoney() {
return Money.of(this.repaymentScheduleDetail().getCurrency(), this.totalOverpaid);
}
public LocalDate getOverpaidOnDate() { public LocalDate getOverpaidOnDate() {
return this.overpaidOnDate; return this.overpaidOnDate;
} }
@@ -5539,7 +5545,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
getActiveCharges()); getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -5782,7 +5788,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) { for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction)); copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
} }
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), copyTransactions, getCurrency(), loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(), copyTransactions, getCurrency(),
getRepaymentScheduleInstallments(), getActiveCharges()); getRepaymentScheduleInstallments(), getActiveCharges());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -6382,11 +6388,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
// If is a refund // If is a refund
if (adjustedTransaction == null) { if (adjustedTransaction == null) {
loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), getRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, getCurrency(),
getActiveCharges()); getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
} else { } else {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges()); allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
@@ -6411,13 +6417,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategyCode); .determineProcessor(this.transactionProcessingStrategyCode);
final Money overpaidAmount = MathUtil.negativeToZero(calculateTotalOverpayment()); // Before Transaction
if (chargebackTransaction.isNotZero(loanCurrency())) { addLoanTransaction(chargebackTransaction);
addLoanTransaction(chargebackTransaction); loanRepaymentScheduleTransactionProcessor.processLatestTransaction(chargebackTransaction, getCurrency(),
} getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
loanRepaymentScheduleTransactionProcessor.handleChargeback(chargebackTransaction, getCurrency(), overpaidAmount,
getRepaymentScheduleInstallments());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
if (!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), loanLifecycleStateMachine)) { if (!doPostLoanTransactionChecks(chargebackTransaction.getTransactionDate(), loanLifecycleStateMachine)) {
@@ -36,15 +36,22 @@ public interface LoanRepaymentScheduleTransactionProcessor {
boolean accept(String s); boolean accept(String s);
void handleTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, /**
Set<LoanCharge> charges); * Provides support for processing the latest transaction (which should be latest transaction) against the loan
* schedule.
*/
void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, Money overpaidAmount);
ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, /**
* Provides support for passing all {@link LoanTransaction}'s so it will completely re-process the entire loan
* schedule. This is required in cases where the {@link LoanTransaction} being processed is in the past and falls
* before existing transactions or and adjustment is made to an existing in which case the entire loan schedule
* needs to be re-processed.
*/
ChangedTransactionDetail reprocessLoanTranactions(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges); MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency, Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges); List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges);
@@ -52,14 +59,4 @@ public interface LoanRepaymentScheduleTransactionProcessor {
* Used in interest recalculation to introduce new interest only installment. * Used in interest recalculation to introduce new interest only installment.
*/ */
boolean isInterestFirstRepaymentScheduleTransactionProcessor(); boolean isInterestFirstRepaymentScheduleTransactionProcessor();
void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments,
Set<LoanCharge> charges);
void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
List<LoanRepaymentScheduleInstallment> installments);
void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges);
} }
@@ -63,15 +63,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return getCode().equalsIgnoreCase(s) || getName().equalsIgnoreCase(s); return getCode().equalsIgnoreCase(s) || getName().equalsIgnoreCase(s);
} }
/**
* Provides support for passing all {@link LoanTransaction}'s so it will completely re-process the entire loan
* schedule. This is required in cases where the {@link LoanTransaction} being processed is in the past and falls
* before existing transactions or and adjustment is made to an existing in which case the entire loan schedule
* needs to be re-processed.
*/
@Override @Override
public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, public ChangedTransactionDetail reprocessLoanTranactions(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency, final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) { final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
@@ -155,20 +148,15 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanTransaction loanTransaction : transactionsToBeProcessed) { for (final LoanTransaction loanTransaction : transactionsToBeProcessed) {
// TODO: analyze and remove this // TODO: analyze and remove this
if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) { if (!loanTransaction.getTypeOf().equals(LoanTransactionType.REFUND_FOR_ACTIVE_LOAN)) {
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() { final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator
.comparing(LoanRepaymentScheduleInstallment::getDueDate);
@Override installments.sort(byDate);
public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
return ord1.getDueDate().compareTo(ord2.getDueDate());
}
};
Collections.sort(installments, byDate);
} }
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
// pass through for new transactions // pass through for new transactions
if (loanTransaction.getId() == null) { if (loanTransaction.getId() == null) {
handleTransaction(loanTransaction, currency, installments, charges); processLatestTransaction(loanTransaction, currency, installments, charges, null);
loanTransaction.adjustInterestComponent(currency); loanTransaction.adjustInterestComponent(currency);
} else { } else {
/** /**
@@ -179,7 +167,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
// Reset derived component of new loan transaction and // Reset derived component of new loan transaction and
// re-process transaction // re-process transaction
handleTransaction(newLoanTransaction, currency, installments, charges); processLatestTransaction(newLoanTransaction, currency, installments, charges, null);
newLoanTransaction.adjustInterestComponent(currency); newLoanTransaction.adjustInterestComponent(currency);
/** /**
* Check if the transaction amounts have changed. If so, reverse the original transaction and update * Check if the transaction amounts have changed. If so, reverse the original transaction and update
@@ -212,6 +200,134 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return changedTransactionDetail; return changedTransactionDetail;
} }
@Override
public void processLatestTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, Money overpaidAmount) {
switch (loanTransaction.getTypeOf()) {
case WRITEOFF -> handleWriteOff(loanTransaction, currency, installments);
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, currency, installments, charges);
case CHARGEBACK -> handleChargeback(loanTransaction, currency, overpaidAmount, installments);
default -> {
Money transactionAmountUnprocessed = handleTransactionAndCharges(loanTransaction, currency, installments, charges, null,
false);
if (transactionAmountUnprocessed.isGreaterThanZero()) {
if (loanTransaction.isWaiver()) {
loanTransaction.updateComponentsAndTotal(transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero(),
transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero());
} else {
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
}
}
}
}
}
@Override
public Money handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
Money unProcessed = Money.zero(currency);
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
if (loanTransaction.isInterestWaiver()) {
processTransaction(loanTransaction, currency, installments, loanCharges, null);
} else {
unProcessed = processTransaction(loanTransaction, currency, installments, loanCharges, null);
}
}
return unProcessed;
}
@Override
public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
return false;
}
// abstract interface
/**
* For early/'in advance' repayments.
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* For normal on-time repayments.
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* For late repayments, how should components of installment be paid off
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* Invoked when a transaction results in an over-payment of the full loan.
*
* transaction amount is greater than the total expected principal and interest of the loan.
*/
@SuppressWarnings("unused")
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// empty implementation by default.
}
/**
* Invoked when a there is a refund of an active loan or undo of an active loan
*
* Undoes principal, interest, fees and charges of this transaction based on the repayment strategy
*
* @param transactionMappings
* TODO
*
*/
protected abstract Money handleRefundTransactionPaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
/**
* This method is responsible for checking if the current transaction is 'an advance/early payment' based on the
* details passed through.
*
* Default implementation is check transaction date is before installment due date.
*/
protected boolean isTransactionInAdvanceOfInstallment(final int installmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/**
* This method is responsible for checking if the current transaction is 'an advance/early payment' based on the
* details passed through.
*
* Default implementation simply processes transactions as 'Late' if the transaction date is after the installment
* due date.
*/
protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
return transactionDate.isAfter(currentInstallment.getDueDate());
}
private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction, private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) { MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction); final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
@@ -422,42 +538,9 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
} }
/**
* Provides support for processing the latest transaction (which should be latest transaction) against the loan
* schedule.
*/
@Override
public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
final Money amountToProcess = null;
final boolean isChargeAmount = false;
handleTransaction(loanTransaction, currency, installments, charges, amountToProcess, isChargeAmount);
}
private void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
final boolean isFeeCharge) {
Money transactionAmountUnprocessed = handleTransactionAndCharges(loanTransaction, currency, installments, charges,
chargeAmountToProcess, isFeeCharge);
if (transactionAmountUnprocessed.isGreaterThanZero()) {
if (loanTransaction.isWaiver()) {
loanTransaction.updateComponentsAndTotal(transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero(),
transactionAmountUnprocessed.zero(), transactionAmountUnprocessed.zero());
} else {
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
}
}
}
private Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency, private Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess, final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
final boolean isFeeCharge) { final boolean isFeeCharge) {
// to.
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
} }
@@ -505,20 +588,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (transactionAmountUnprocessed.isGreaterThanZero()) { if (transactionAmountUnprocessed.isGreaterThanZero()) {
if (currentInstallment.isNotFullyPaidOff()) { if (currentInstallment.isNotFullyPaidOff()) {
// is this transaction early/late/on-time with respect to
// the
// current installment?
if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate)) { if (isTransactionInAdvanceOfInstallment(installmentIndex, installments, transactionDate)) {
transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment, transactionAmountUnprocessed = handleTransactionThatIsPaymentInAdvanceOfInstallment(currentInstallment,
installments, loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); installments, loanTransaction, transactionAmountUnprocessed, transactionMappings, charges);
} else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, transactionDate)) { } else if (isTransactionALateRepaymentOnInstallment(installmentIndex, installments, transactionDate)) {
// does this result in a late payment of existing
// installment?
transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments, transactionAmountUnprocessed = handleTransactionThatIsALateRepaymentOfInstallment(currentInstallment, installments,
loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); loanTransaction, transactionAmountUnprocessed, transactionMappings, charges);
} else { } else {
// standard transaction
transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment, transactionAmountUnprocessed = handleTransactionThatIsOnTimePaymentOfInstallment(currentInstallment,
loanTransaction, transactionAmountUnprocessed, transactionMappings, charges); loanTransaction, transactionAmountUnprocessed, transactionMappings, charges);
} }
@@ -611,8 +687,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return earliestUnpaidCharge; return earliestUnpaidCharge;
} }
@Override private void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
public void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments) { final List<LoanRepaymentScheduleInstallment> installments) {
final LocalDate transactionDate = loanTransaction.getTransactionDate(); final LocalDate transactionDate = loanTransaction.getTransactionDate();
@@ -637,121 +712,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion); loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
} }
// abstract interface private void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
/**
* This method is responsible for checking if the current transaction is 'an advance/early payment' based on the
* details passed through.
*
* Default implementation simply processes transactions as 'Late' if the transaction date is after the installment
* due date.
*/
protected boolean isTransactionALateRepaymentOnInstallment(final int installmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(installmentIndex);
return transactionDate.isAfter(currentInstallment.getDueDate());
}
/**
* For late repayments, how should components of installment be paid off
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsALateRepaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* This method is responsible for checking if the current transaction is 'an advance/early payment' based on the
* details passed through.
*
* Default implementation is check transaction date is before installment due date.
*/
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/**
* For early/'in advance' repayments.
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsPaymentInAdvanceOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
List<LoanRepaymentScheduleInstallment> installments, LoanTransaction loanTransaction, Money paymentInAdvance,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* For normal on-time repayments.
*
* @param transactionMappings
* TODO
* @param charges
*/
protected abstract Money handleTransactionThatIsOnTimePaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, Set<LoanCharge> charges);
/**
* Invoked when a transaction results in an over-payment of the full loan.
*
* transaction amount is greater than the total expected principal and interest of the loan.
*/
@SuppressWarnings("unused")
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// empty implementation by default.
}
@Override
public Money handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
Money unProcessed = Money.zero(currency);
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
Money amountToProcess = null;
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
if (loanTransaction.isInterestWaiver()) {
processTransaction(loanTransaction, currency, installments, loanCharges, amountToProcess);
} else {
unProcessed = processTransaction(loanTransaction, currency, installments, loanCharges, amountToProcess);
}
}
return unProcessed;
}
@Override
public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
return false;
}
@Override
public void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
List<LoanRepaymentScheduleInstallment> installments) { List<LoanRepaymentScheduleInstallment> installments) {
processCreditTransaction(loanTransaction, overpaidAmount, currency, installments); processCreditTransaction(loanTransaction, overpaidAmount, currency, installments);
} }
@Override private void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
public void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) { List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
// TODO Auto-generated method stub
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>(); List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
final Comparator<LoanRepaymentScheduleInstallment> byDate = new Comparator<LoanRepaymentScheduleInstallment>() { final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
installments.sort(Collections.reverseOrder(byDate));
@Override
public int compare(LoanRepaymentScheduleInstallment ord1, LoanRepaymentScheduleInstallment ord2) {
return ord1.getDueDate().compareTo(ord2.getDueDate());
}
};
Collections.sort(installments, Collections.reverseOrder(byDate));
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency); Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
@@ -786,19 +756,6 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings); loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
} }
/**
* Invoked when a there is a refund of an active loan or undo of an active loan
*
* Undoes principal, interest, fees and charges of this transaction based on the repayment strategy
*
* @param transactionMappings
* TODO
*
*/
protected abstract Money handleRefundTransactionPaymentOfInstallment(LoanRepaymentScheduleInstallment currentInstallment,
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings);
private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges, private void undoChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
final Integer installmentNumber) { final Integer installmentNumber) {
@@ -853,91 +810,4 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
return latestPaidCharge; return latestPaidCharge;
} }
@Override
public void processTransactionsFromDerivedFields(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (!loanTransaction.isAccrualTransaction()) {
processTransactionFromDerivedFields(loanTransaction, currency, installments, charges);
}
}
}
private void processTransactionFromDerivedFields(final LoanTransaction loanTransaction, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
Money principal = loanTransaction.getPrincipalPortion(currency);
Money interest = loanTransaction.getInterestPortion(currency);
if (loanTransaction.isInterestWaiver()) {
interest = loanTransaction.getAmount(currency);
}
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
final LocalDate transactionDate = loanTransaction.getTransactionDate();
if (principal.isGreaterThanZero() || interest.isGreaterThanZero() || feeCharges.isGreaterThanZero()
|| penaltyCharges.isGreaterThanZero()) {
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (currentInstallment.isNotFullyPaidOff()) {
if (penaltyCharges.isGreaterThanZero()) {
Money penaltyChargesPortion = Money.zero(currency);
if (loanTransaction.isWaiver()) {
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate, penaltyCharges);
} else {
penaltyChargesPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, penaltyCharges);
}
penaltyCharges = penaltyCharges.minus(penaltyChargesPortion);
}
if (feeCharges.isGreaterThanZero()) {
Money feeChargesPortion = Money.zero(currency);
if (loanTransaction.isWaiver()) {
feeChargesPortion = currentInstallment.waiveFeeChargesComponent(transactionDate, feeCharges);
} else {
feeChargesPortion = currentInstallment.payFeeChargesComponent(transactionDate, feeCharges);
}
feeCharges = feeCharges.minus(feeChargesPortion);
}
if (interest.isGreaterThanZero()) {
Money interestPortion = Money.zero(currency);
if (loanTransaction.isWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, interest);
} else {
interestPortion = currentInstallment.payInterestComponent(transactionDate, interest);
}
interest = interest.minus(interestPortion);
}
if (principal.isGreaterThanZero()) {
Money principalPortion = currentInstallment.payPrincipalComponent(transactionDate, principal);
principal = principal.minus(principalPortion);
}
}
if (!(principal.isGreaterThanZero() || interest.isGreaterThanZero() || feeCharges.isGreaterThanZero()
|| penaltyCharges.isGreaterThanZero())) {
break;
}
}
}
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
Integer installmentNumber = null;
if (loanTransaction.isChargePayment() && installments.size() == 1) {
installmentNumber = installments.get(0).getInstallmentNumber();
}
if (loanTransaction.isNotWaiver()) {
feeCharges = loanTransaction.getFeeChargesPortion(currency);
penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (feeCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
}
if (penaltyCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
}
}
}
} }
@@ -18,10 +18,13 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl; package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
import java.time.LocalDate;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.NotImplementedException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -69,4 +72,22 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) { List<LoanTransactionToRepaymentScheduleMapping> transactionMappings) {
throw new NotImplementedException(); throw new NotImplementedException();
} }
@Override
public ChangedTransactionDetail reprocessLoanTranactions(LocalDate disbursementDate, List<LoanTransaction> transactionsPostDisbursement,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
throw new NotImplementedException();
}
@Override
public void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, Money overpaidAmount) {
throw new NotImplementedException();
}
@Override
public Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
return super.handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, loanCharges);
}
} }
@@ -56,18 +56,6 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
return STRATEGY_NAME; return STRATEGY_NAME;
} }
/**
* For creocore, early is defined as any date before the installment due date
*/
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/** /**
* For early/'in advance' repayments, pay off in the same way as on-time payments, interest first then principal. * For early/'in advance' repayments, pay off in the same way as on-time payments, interest first then principal.
*/ */
@@ -153,12 +141,6 @@ public class CreocoreLoanRepaymentScheduleTransactionProcessor extends AbstractL
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@SuppressWarnings("unused")
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// dont do anything for with loan over-payment
}
@Override @Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
@@ -245,11 +245,6 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override @Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
@@ -247,11 +247,6 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override @Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
@@ -56,15 +56,6 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
return STRATEGY_NAME; return STRATEGY_NAME;
} }
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/** /**
* For early/'in advance' repayments, pay off in the same way as on-time payments, interest first then principal. * For early/'in advance' repayments, pay off in the same way as on-time payments, interest first then principal.
*/ */
@@ -151,11 +142,6 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// TODO - KW - dont do anything with loan over-payment for now
}
@Override @Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
@@ -164,10 +150,10 @@ public class FineractStyleLoanRepaymentScheduleTransactionProcessor extends Abst
final LocalDate transactionDate = loanTransaction.getTransactionDate(); final LocalDate transactionDate = loanTransaction.getTransactionDate();
final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency(); final MonetaryCurrency currency = transactionAmountUnprocessed.getCurrency();
Money transactionAmountRemaining = transactionAmountUnprocessed; Money transactionAmountRemaining = transactionAmountUnprocessed;
Money principalPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money principalPortion = Money.zero(currency);
Money interestPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money interestPortion = Money.zero(currency);
Money feeChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money feeChargesPortion = Money.zero(currency);
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money penaltyChargesPortion = Money.zero(currency);
principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining); principalPortion = currentInstallment.unpayPrincipalComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion); transactionAmountRemaining = transactionAmountRemaining.minus(principalPortion);
@@ -216,9 +216,6 @@ public class HeavensFamilyLoanRepaymentScheduleTransactionProcessor extends Abst
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {}
@Override @Override
protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment, protected Money handleRefundTransactionPaymentOfInstallment(final LoanRepaymentScheduleInstallment currentInstallment,
final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed, final LoanTransaction loanTransaction, final Money transactionAmountUnprocessed,
@@ -59,19 +59,6 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
return STRATEGY_NAME; return STRATEGY_NAME;
} }
/**
* For creocore, early is defined as any date before the installment due date
*/
@SuppressWarnings("unused")
@Override
protected boolean isTransactionInAdvanceOfInstallment(final int currentInstallmentIndex,
final List<LoanRepaymentScheduleInstallment> installments, final LocalDate transactionDate) {
final LoanRepaymentScheduleInstallment currentInstallment = installments.get(currentInstallmentIndex);
return transactionDate.isBefore(currentInstallment.getDueDate());
}
/** /**
* For early/'in advance' repayments, pays off principal component only. * For early/'in advance' repayments, pays off principal component only.
*/ */
@@ -259,12 +246,6 @@ public class RBILoanRepaymentScheduleTransactionProcessor extends AbstractLoanRe
return transactionAmountRemaining; return transactionAmountRemaining;
} }
@SuppressWarnings("unused")
@Override
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// dont do anything for with loan over-payment
}
@Override @Override
public boolean isInterestFirstRepaymentScheduleTransactionProcessor() { public boolean isInterestFirstRepaymentScheduleTransactionProcessor() {
return true; return true;
@@ -2808,8 +2808,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill); loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsPostDisbursementExcludeAccruals(); List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsPostDisbursementExcludeAccruals();
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(loanApplicationTerms.getExpectedDisbursementDate(),
currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges()); loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
Money feeCharges = Money.zero(currency); Money feeCharges = Money.zero(currency);
Money penaltyCharges = Money.zero(currency); Money penaltyCharges = Money.zero(currency);
Money totalPrincipal = Money.zero(currency); Money totalPrincipal = Money.zero(currency);
@@ -809,8 +809,8 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan); defaultLoanLifecycleStateMachine.transition(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, loan);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(loan.transactionProcessingStrategy()); .determineProcessor(loan.transactionProcessingStrategy());
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanChargeAdjustmentTransaction, loan.getCurrency(), loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanChargeAdjustmentTransaction, loan.getCurrency(),
loan.getRepaymentScheduleInstallments(), loan.getActiveCharges()); loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), loan.getTotalOverpaidAsMoney());
loan.addLoanTransaction(loanChargeAdjustmentTransaction); loan.addLoanTransaction(loanChargeAdjustmentTransaction);
loan.updateLoanSummaryAndStatus(); loan.updateLoanSummaryAndStatus();