FINERACT-1981: Disbursement on overpaid loan
This commit is contained in:
+6
-5
@@ -2483,7 +2483,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail, boolean downPaymentEnabled) {
|
||||
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) {
|
||||
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE);
|
||||
|
||||
@@ -2501,7 +2501,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
|
||||
|
||||
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
|
||||
isDisbursementMissed(), scheduleGeneratorDTO, downPaymentEnabled);
|
||||
isDisbursementMissed(), scheduleGeneratorDTO);
|
||||
|
||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -2533,7 +2533,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
|
||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean downPaymentEnabled) {
|
||||
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
|
||||
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
|
||||
if ((interestRecalculationEnabledParam && (DateUtils.isBeforeBusinessDate(firstInstallmentDueDate) || disbursementMissedParam))) {
|
||||
@@ -3837,7 +3837,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
if (loanTransaction.isRefund() || loanTransaction.isRefundForActiveLoan()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getAmount(currency));
|
||||
} else if (loanTransaction.isCreditBalanceRefund() || loanTransaction.isChargeback()) {
|
||||
} else if (loanTransaction.isCreditBalanceRefund() || loanTransaction.isChargeback() || loanTransaction.isDisbursement()) {
|
||||
totalPaidInRepayments = totalPaidInRepayments.minus(loanTransaction.getOverPaymentPortion(currency));
|
||||
}
|
||||
}
|
||||
@@ -5940,7 +5940,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
List<LoanTransaction> loanTransactions = retrieveListOfTransactionsExcludeAccruals();
|
||||
for (LoanTransaction loanTransaction : loanTransactions) {
|
||||
if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) {
|
||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()));
|
||||
outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency()))
|
||||
.minus(loanTransaction.getOverPaymentPortion(getCurrency()));
|
||||
loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount());
|
||||
} else if (loanTransaction.isChargeback() || loanTransaction.isCreditBalanceRefund()) {
|
||||
Money transactionOutstanding = loanTransaction.getAmount(getCurrency());
|
||||
|
||||
+36
-16
@@ -154,7 +154,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
public void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, MoneyHolder overpaymentHolder) {
|
||||
switch (loanTransaction.getTypeOf()) {
|
||||
case DISBURSEMENT -> handleDisbursement(loanTransaction, currency, installments);
|
||||
case DISBURSEMENT -> handleDisbursement(loanTransaction, currency, installments, overpaymentHolder);
|
||||
case WRITEOFF -> handleWriteOff(loanTransaction, currency, installments);
|
||||
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, currency, installments, charges);
|
||||
case CHARGEBACK -> handleChargeback(loanTransaction, currency, installments, overpaymentHolder);
|
||||
@@ -281,12 +281,12 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
private void handleDisbursement(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
updateLoanSchedule(loanTransaction, currency, installments);
|
||||
List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
|
||||
updateLoanSchedule(loanTransaction, currency, installments, overpaymentHolder);
|
||||
}
|
||||
|
||||
private void updateLoanSchedule(LoanTransaction disbursementTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
List<LoanRepaymentScheduleInstallment> installments, MoneyHolder overpaymentHolder) {
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
List<LoanRepaymentScheduleInstallment> candidateRepaymentInstallments = installments.stream().filter(
|
||||
i -> i.getDueDate().isAfter(disbursementTransaction.getTransactionDate()) && !i.isDownPayment() && !i.isAdditional())
|
||||
@@ -304,23 +304,43 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
downPaymentAmt = Money.roundToMultiplesOf(downPaymentAmt, installmentAmountInMultiplesOf);
|
||||
}
|
||||
downPaymentAmount = Money.of(currency, downPaymentAmt);
|
||||
Money autoPayFromOverpayment = overpaymentHolder.getMoneyObject();
|
||||
downPaymentInstallment.addToPrincipal(disbursementTransaction.getTransactionDate(), downPaymentAmount);
|
||||
|
||||
Money paidAmount = downPaymentInstallment.payPrincipalComponent(disbursementTransaction.getTransactionDate(),
|
||||
autoPayFromOverpayment);
|
||||
disbursementTransaction.setOverPayments(paidAmount);
|
||||
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(paidAmount));
|
||||
}
|
||||
Money amortizableAmount = disbursementTransaction.getAmount(currency).minus(downPaymentAmount);
|
||||
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode());
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
increasePrincipalBy = Money.roundToMultiplesOf(increasePrincipalBy, installmentAmountInMultiplesOf);
|
||||
Money overpaidAmount = overpaymentHolder.getMoneyObject();
|
||||
if (overpaidAmount.isGreaterThanZero()) {
|
||||
if (amortizableAmount.isGreaterThan(overpaidAmount)) {
|
||||
overpaymentHolder.setMoneyObject(Money.zero(currency));
|
||||
amortizableAmount = amortizableAmount.minus(overpaidAmount);
|
||||
disbursementTransaction.setOverPayments(disbursementTransaction.getOverPaymentPortion(currency).add(overpaidAmount));
|
||||
} else {
|
||||
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(amortizableAmount));
|
||||
amortizableAmount = Money.zero(currency);
|
||||
disbursementTransaction.setOverPayments(disbursementTransaction.getOverPaymentPortion(currency).add(amortizableAmount));
|
||||
}
|
||||
}
|
||||
Money remainingAmount = amortizableAmount
|
||||
.minus(increasePrincipalBy.multiplyRetainScale(noCandidateRepaymentInstallments, mc.getRoundingMode()));
|
||||
|
||||
Money finalIncreasePrincipalBy = increasePrincipalBy;
|
||||
candidateRepaymentInstallments
|
||||
.forEach(i -> i.addToPrincipal(disbursementTransaction.getTransactionDate(), finalIncreasePrincipalBy));
|
||||
// Hence the rounding, we might need to amend the last installment amount
|
||||
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
|
||||
.addToPrincipal(disbursementTransaction.getTransactionDate(), remainingAmount);
|
||||
if (amortizableAmount.isGreaterThanZero()) {
|
||||
Money increasePrincipalBy = amortizableAmount.dividedBy(noCandidateRepaymentInstallments, mc.getRoundingMode());
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
increasePrincipalBy = Money.roundToMultiplesOf(increasePrincipalBy, installmentAmountInMultiplesOf);
|
||||
}
|
||||
|
||||
Money remainingAmount = amortizableAmount
|
||||
.minus(increasePrincipalBy.multiplyRetainScale(noCandidateRepaymentInstallments, mc.getRoundingMode()));
|
||||
|
||||
Money finalIncreasePrincipalBy = increasePrincipalBy;
|
||||
candidateRepaymentInstallments
|
||||
.forEach(i -> i.addToPrincipal(disbursementTransaction.getTransactionDate(), finalIncreasePrincipalBy));
|
||||
// Hence the rounding, we might need to amend the last installment amount
|
||||
candidateRepaymentInstallments.get(noCandidateRepaymentInstallments - 1)
|
||||
.addToPrincipal(disbursementTransaction.getTransactionDate(), remainingAmount);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
|
||||
Reference in New Issue
Block a user