[FINERACT-1958] Repayment schedule for down payment

This commit is contained in:
taskain7
2023-08-18 14:51:54 +02:00
parent b616a96fc9
commit baa447196e
15 changed files with 758 additions and 47 deletions
@@ -1264,13 +1264,14 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
this.repaymentScheduleInstallments.clear();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(this,
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(),
scheduledLoanInstallment.getLoanCompoundingDetails());
scheduledLoanInstallment.getLoanCompoundingDetails(), scheduledLoanInstallment.rescheduleInterestPortion(),
scheduledLoanInstallment.isDownPaymentPeriod());
addLoanRepaymentScheduleInstallment(installment);
}
}
@@ -2477,7 +2478,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) {
final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail, boolean downPaymentEnabled) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed(ACTUAL_DISBURSEMENT_DATE);
@@ -2495,7 +2496,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(this.repaymentScheduleDetail().isInterestRecalculationEnabled(),
isDisbursementMissed(), scheduleGeneratorDTO);
isDisbursementMissed(), scheduleGeneratorDTO, downPaymentEnabled);
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
@@ -2527,14 +2528,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
private void regenerateRepaymentScheduleWithInterestRecalculationIfNeeded(boolean interestRecalculationEnabledParam,
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO) {
boolean disbursementMissedParam, ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean downPaymentEnabled) {
LocalDate firstInstallmentDueDate = fetchRepaymentScheduleInstallment(1).getDueDate();
if (interestRecalculationEnabledParam
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam)) {
if ((interestRecalculationEnabledParam
&& (firstInstallmentDueDate.isBefore(DateUtils.getBusinessLocalDate()) || disbursementMissedParam))) {
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else if (downPaymentEnabled) {
LoanScheduleDTO loanSchedule = getRecalculatedSchedule(scheduleGeneratorDTO);
updateLoanSchedule(loanSchedule.getInstallments());
}
}
private List<LoanDisbursementDetails> getDisbursedLoanDisbursementDetails() {
@@ -2965,7 +2968,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
externalId = ExternalId.generate();
}
Money downPaymentMoney = Money.of(getCurrency(),
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 9));
MathUtil.percentageOf(disbursedAmount, disbursedAmountPercentageForDownPayment, 19));
LoanTransaction downPaymentTransaction = LoanTransaction.downPayment(getOffice(), downPaymentMoney, null, disbursedOn,
externalId);
/*
@@ -5553,7 +5556,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
LocalDate lastTransactionDate = getLastUserTransactionDate();
final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO);
if (loanSchedule == null) {
@@ -5788,15 +5790,15 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
}
private LoanScheduleDTO getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff()) {
if (!this.repaymentScheduleDetail().isEnableDownPayment()
&& (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || isChargedOff())) {
return null;
}
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
final RoundingMode roundingMode = MoneyHelper.getRoundingMode();
final MathContext mc = new MathContext(8, roundingMode);
final MathContext mc = new MathContext(19, roundingMode);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO);
@@ -131,6 +131,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
@Column(name = "credits_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal credits;
@Column(name = "is_down_payment", nullable = false)
private boolean isDownPayment;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
@@ -154,6 +157,15 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion) {
this(loan, installmentNumber, fromDate, dueDate, principal, interest, feeCharges, penaltyCharges, recalculatedInterestComponent,
compoundingDetails, rescheduleInterestPortion, false);
}
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
final LocalDate dueDate, final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
final BigDecimal penaltyCharges, final boolean recalculatedInterestComponent,
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails, final BigDecimal rescheduleInterestPortion,
final boolean isDownPayment) {
this.loan = loan;
this.installmentNumber = installmentNumber;
this.fromDate = fromDate;
@@ -169,6 +181,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
}
this.loanCompoundingDetails = compoundingDetails;
this.rescheduleInterestPortion = rescheduleInterestPortion;
this.isDownPayment = isDownPayment;
}
public LoanRepaymentScheduleInstallment(final Loan loan, final Integer installmentNumber, final LocalDate fromDate,
@@ -91,6 +91,11 @@ public final class LoanSchedulePeriodData {
totalInstallmentAmountForPeriod);
}
public static LoanSchedulePeriodData downPaymentOnlyPeriod(final Integer periodNumber, final LocalDate periodDate,
final BigDecimal principalDue, final BigDecimal principalOutstanding) {
return new LoanSchedulePeriodData(periodNumber, periodDate, periodDate, principalDue, principalOutstanding);
}
public static LoanSchedulePeriodData repaymentPeriodWithPayments(@SuppressWarnings("unused") final Long loanId,
final Integer periodNumber, final LocalDate fromDate, final LocalDate dueDate, final LocalDate obligationsMetOnDate,
final boolean complete, final BigDecimal principalOriginalDue, final BigDecimal principalPaid,
@@ -265,6 +270,61 @@ public final class LoanSchedulePeriodData {
this.totalCredits = BigDecimal.ZERO;
}
// TODO refactor the class to builder pattern
private LoanSchedulePeriodData(Integer periodNumber, LocalDate fromDate, LocalDate dueDate, BigDecimal principalDue,
BigDecimal principalOutstanding) {
this.period = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.obligationsMetOnDate = null;
this.complete = null;
this.daysInPeriod = 1;
this.principalDisbursed = null;
this.principalOriginalDue = principalDue;
this.principalDue = principalOriginalDue;
this.principalPaid = null;
this.principalWrittenOff = null;
this.principalOutstanding = principalOriginalDue;
this.principalLoanBalanceOutstanding = principalOutstanding;
this.interestOriginalDue = null;
this.interestDue = null;
this.interestPaid = null;
this.interestWaived = null;
this.interestWrittenOff = null;
this.interestOutstanding = null;
this.feeChargesDue = null;
this.feeChargesPaid = null;
this.feeChargesWaived = null;
this.feeChargesWrittenOff = null;
this.feeChargesOutstanding = null;
this.penaltyChargesDue = null;
this.penaltyChargesPaid = null;
this.penaltyChargesWaived = null;
this.penaltyChargesWrittenOff = null;
this.penaltyChargesOutstanding = null;
this.totalOriginalDueForPeriod = principalDue;
this.totalDueForPeriod = principalDue;
this.totalPaidForPeriod = BigDecimal.ZERO;
this.totalPaidInAdvanceForPeriod = null;
this.totalPaidLateForPeriod = null;
this.totalWaivedForPeriod = null;
this.totalWrittenOffForPeriod = null;
this.totalOutstandingForPeriod = totalDueForPeriod;
this.totalActualCostOfLoanForPeriod = null;
this.totalInstallmentAmountForPeriod = totalDueForPeriod;
if (dueDate.isBefore(DateUtils.getBusinessLocalDate())) {
this.totalOverdue = this.totalOutstandingForPeriod;
} else {
this.totalOverdue = null;
}
this.totalCredits = BigDecimal.ZERO;
}
/*
* Used for creating loan schedule periods with full information on expected principal, interest & charges along
* with what portion of each is paid.
@@ -1737,4 +1737,12 @@ public final class LoanApplicationTerms {
public void setNewScheduledDueDateStart(LocalDate newScheduledDueDateStart) {
this.newScheduledDueDateStart = newScheduledDueDateStart;
}
public boolean isDownPaymentEnabled() {
return isDownPaymentEnabled;
}
public BigDecimal getDisbursedAmountPercentageForDownPayment() {
return disbursedAmountPercentageForDownPayment;
}
}
@@ -31,6 +31,8 @@ public interface LoanScheduleModelPeriod {
boolean isRepaymentPeriod();
boolean isDownPaymentPeriod();
Integer periodNumber();
LocalDate periodFromDate();