Merge pull request #1580 from pramodn02/variableinstalmentchanges

MIFOSX-2358:fix for last installment overdue charge
This commit is contained in:
Adi Narayana Raju
2015-12-29 14:06:58 +05:30
6 changed files with 66 additions and 21 deletions
@@ -836,6 +836,9 @@ public class Loan extends AbstractPersistable<Long> {
*/ */
private BigDecimal calculateAmountPercentageAppliedTo(final LoanCharge loanCharge) { private BigDecimal calculateAmountPercentageAppliedTo(final LoanCharge loanCharge) {
BigDecimal amount = BigDecimal.ZERO; BigDecimal amount = BigDecimal.ZERO;
if(loanCharge.isOverdueInstallmentCharge()){
return loanCharge.getAmountPercentageAppliedTo();
}
switch (loanCharge.getChargeCalculation()) { switch (loanCharge.getChargeCalculation()) {
case PERCENT_OF_AMOUNT: case PERCENT_OF_AMOUNT:
amount = getPrincpal().getAmount(); amount = getPrincpal().getAmount();
@@ -172,7 +172,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// calculated interest start date for the period // calculated interest start date for the period
LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
scheduleParams.getPeriodStartDate(), idealDisbursementDate); scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
// Loan Schedule Exceptions that need to be applied for Loan Account // Loan Schedule Exceptions that need to be applied for Loan Account
LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams,
@@ -229,14 +229,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// for a loan repayment which falls between the // for a loan repayment which falls between the
// two periods for interest first repayment strategies // two periods for interest first repayment strategies
handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods, handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods,
totalInterestChargedForFullLoanTerm, idealDisbursementDate, lastRestDate, scheduledDueDate, totalInterestChargedForFullLoanTerm, idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate,
periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams);
if (currentPeriodParams.isSkipCurrentLoop()) { if (currentPeriodParams.isSkipCurrentLoop()) {
continue; continue;
} }
periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
scheduleParams.getPeriodStartDate(), idealDisbursementDate); scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
// backup for pre-close transaction // backup for pre-close transaction
updateCompoundingDetails(scheduleParams, periodStartDateApplicableForInterest); updateCompoundingDetails(scheduleParams, periodStartDateApplicableForInterest);
@@ -581,9 +581,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams,
final Collection<LoanScheduleModelPeriod> periods, final Money totalInterestChargedForFullLoanTerm, final Collection<LoanScheduleModelPeriod> periods, final Money totalInterestChargedForFullLoanTerm,
final LocalDate idealDisbursementDate, final LocalDate lastRestDate, final LocalDate scheduledDueDate, final LocalDate idealDisbursementDate, LocalDate firstRepaymentdate, final LocalDate lastRestDate,
final LocalDate periodStartDateForInterest, final Collection<RecalculationDetail> applicableTransactions, final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest,
final ScheduleCurrentPeriodParams currentPeriodParams) { final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) {
if (scheduleParams.applyInterestRecalculation()) { if (scheduleParams.applyInterestRecalculation()) {
final MonetaryCurrency currency = scheduleParams.getCurrency(); final MonetaryCurrency currency = scheduleParams.getCurrency();
final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges(); final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
@@ -608,7 +608,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// calculates period start date for interest // calculates period start date for interest
// calculation as per the configuration // calculation as per the configuration
periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
scheduleParams.getPeriodStartDate(), idealDisbursementDate); scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate);
int daysInPeriodApplicable = Days.daysBetween(periodStartDateApplicableForInterest, transactionDate).getDays(); int daysInPeriodApplicable = Days.daysBetween(periodStartDateApplicableForInterest, transactionDate).getDays();
Money interestForThisinstallment = Money.zero(currency); Money interestForThisinstallment = Money.zero(currency);
@@ -1507,15 +1507,17 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
/** /**
* calculates Interest stating date as per the settings * calculates Interest stating date as per the settings
*
* @param firstRepaymentdate
* TODO
*/ */
private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate, private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate,
final LocalDate idealDisbursementDate) { final LocalDate idealDisbursementDate, final LocalDate firstRepaymentdate) {
LocalDate periodStartDateApplicableForInterest = periodStartDate; LocalDate periodStartDateApplicableForInterest = periodStartDate;
if (periodStartDate.isBefore(idealDisbursementDate)) { if (periodStartDate.isBefore(idealDisbursementDate) || firstRepaymentdate.isAfter(periodStartDate)) {
if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) {
if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate()) if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())
|| loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate().isBefore( || loanApplicationTerms.getInterestChargedFromLocalDate().isAfter(periodStartDate)) {
loanApplicationTerms.getInterestChargedFromLocalDate())) {
periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate();
} }
} else { } else {
@@ -146,13 +146,13 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
dataValidationErrors.add(error); dataValidationErrors.add(error);
} else if (repaymentsStartingFromDate == null && interestChargedFromDate != null) { } else if (repaymentsStartingFromDate == null && interestChargedFromDate != null) {
if (expectedDisbursementDate != null && expectedDisbursementDate.isAfter(interestChargedFromDate)) { /* if (expectedDisbursementDate != null && expectedDisbursementDate.isAfter(interestChargedFromDate)) {
final ApiParameterError error = ApiParameterError.parameterError( final ApiParameterError error = ApiParameterError.parameterError(
"validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date", "validation.msg.loan.interestChargedFromDate.cannot.be.before.disbursement.date",
"The parameter interestChargedFromDate cannot be before the date given for expectedDisbursementDate.", "The parameter interestChargedFromDate cannot be before the date given for expectedDisbursementDate.",
"interestChargedFromDate", interestChargedFromDate, expectedDisbursementDate); "interestChargedFromDate", interestChargedFromDate, expectedDisbursementDate);
dataValidationErrors.add(error); dataValidationErrors.add(error);
} }*/
} }
} }
@@ -1482,6 +1482,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final StringBuilder sqlBuilder = new StringBuilder(400); final StringBuilder sqlBuilder = new StringBuilder(400);
sqlBuilder.append("select ").append(rm.schema()).append(" where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate ") sqlBuilder.append("select ").append(rm.schema()).append(" where DATE_SUB(CURDATE(),INTERVAL ? DAY) > ls.duedate ")
.append(" and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 ") .append(" and ls.completed_derived <> 1 and mc.charge_applies_to_enum =1 ")
.append(" and ls.recalculated_interest_component <> 1 ")
.append(" and mc.charge_time_enum = 9 and ml.loan_status_id = 300 "); .append(" and mc.charge_time_enum = 9 and ml.loan_status_id = 300 ");
if (backdatePenalties) { return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod }); } if (backdatePenalties) { return this.jdbcTemplate.query(sqlBuilder.toString(), rm, new Object[] { penaltyWaitPeriod }); }
@@ -2474,6 +2474,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
boolean runInterestRecalculation = false; boolean runInterestRecalculation = false;
LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant(); LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
LocalDate lastChargeDate = null;
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) { for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) {
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString()); final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString());
@@ -2486,6 +2487,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (recalculateFrom.isAfter(overdueDTO.getRecalculateFrom())) { if (recalculateFrom.isAfter(overdueDTO.getRecalculateFrom())) {
recalculateFrom = overdueDTO.getRecalculateFrom(); recalculateFrom = overdueDTO.getRecalculateFrom();
} }
if (lastChargeDate == null || overdueDTO.getLastChargeAppliedDate().isAfter(lastChargeDate)) {
lastChargeDate = overdueDTO.getLastChargeAppliedDate();
}
} }
if (loan != null) { if (loan != null) {
boolean reprocessRequired = true; boolean reprocessRequired = true;
@@ -2503,6 +2507,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
if (reprocessRequired) { if (reprocessRequired) {
addInstallmentIfPenaltyAppliedAfterLastDueDate(loan, lastChargeDate);
ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions(); ChangedTransactionDetail changedTransactionDetail = loan.reprocessTransactions();
if (changedTransactionDetail != null) { if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -2528,6 +2533,28 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
private void addInstallmentIfPenaltyAppliedAfterLastDueDate(Loan loan, LocalDate lastChargeDate) {
if (lastChargeDate != null) {
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
LoanRepaymentScheduleInstallment lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size());
if (lastChargeDate.isAfter(lastInstallment.getDueDate())) {
if (lastInstallment.isRecalculatedInterestComponent()) {
installments.remove(lastInstallment);
lastInstallment = loan.fetchRepaymentScheduleInstallment(installments.size());
}
boolean recalculatedInterestComponent = true;
BigDecimal principal = BigDecimal.ZERO;
BigDecimal interest = BigDecimal.ZERO;
BigDecimal feeCharges = BigDecimal.ZERO;
BigDecimal penaltyCharges = BigDecimal.ONE;
LoanRepaymentScheduleInstallment newEntry = new LoanRepaymentScheduleInstallment(loan, installments.size() + 1,
lastInstallment.getDueDate(), lastChargeDate, principal, interest, feeCharges, penaltyCharges,
recalculatedInterestComponent);
installments.add(newEntry);
}
}
}
public LoanOverdueDTO applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber, public LoanOverdueDTO applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber,
final JsonCommand command, Loan loan, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) { final JsonCommand command, Loan loan, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
boolean runInterestRecalculation = false; boolean runInterestRecalculation = false;
@@ -2565,6 +2592,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
LoanRepaymentScheduleInstallment installment = null; LoanRepaymentScheduleInstallment installment = null;
LocalDate lastChargeAppliedDate = dueDate;
if (!scheduleDates.isEmpty()) { if (!scheduleDates.isEmpty()) {
if (loan == null) { if (loan == null) {
loan = this.loanAssembler.assembleFrom(loanId); loan = this.loanAssembler.assembleFrom(loanId);
@@ -2573,8 +2601,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
} }
installment = loan.fetchRepaymentScheduleInstallment(periodNumber); installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
lastChargeAppliedDate = installment.getDueDate();
} }
LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant(); LocalDate recalculateFrom = DateUtils.getLocalDateOfTenant();
if (loan != null) { if (loan != null) {
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE, this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_APPLY_OVERDUE_CHARGE,
constructEntityMap(BUSINESS_ENTITY.LOAN, loan)); constructEntityMap(BUSINESS_ENTITY.LOAN, loan));
@@ -2591,10 +2621,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (entry.getValue().isBefore(recalculateFrom)) { if (entry.getValue().isBefore(recalculateFrom)) {
recalculateFrom = entry.getValue(); recalculateFrom = entry.getValue();
} }
if (entry.getValue().isAfter(lastChargeAppliedDate)) {
lastChargeAppliedDate = entry.getValue();
}
} }
} }
return new LoanOverdueDTO(loan, runInterestRecalculation, recalculateFrom); return new LoanOverdueDTO(loan, runInterestRecalculation, recalculateFrom, lastChargeAppliedDate);
} }
@Override @Override
@@ -3079,11 +3112,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
boolean isFloatingInterestRate = loan.getIsFloatingInterestRate(); boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
BigDecimal interestRateDiff = loan.getInterestRateDifferential(); BigDecimal interestRateDiff = loan.getInterestRateDifferential();
List<FloatingRatePeriodData> baseLendingRatePeriods = null; List<FloatingRatePeriodData> baseLendingRatePeriods = null;
try{ try {
baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate().getRatePeriods();
.getRatePeriods(); } catch (final FloatingRateNotFoundException ex) {
}catch(final FloatingRateNotFoundException ex){ // Do not do anything
// Do not do anything
} }
floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff, floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, loan.getDisbursementDate(), interestRateDiff,
baseLendingRatePeriods); baseLendingRatePeriods);
@@ -13,11 +13,14 @@ public class LoanOverdueDTO {
private final Loan loan; private final Loan loan;
private final boolean runInterestRecalculation; private final boolean runInterestRecalculation;
private final LocalDate recalculateFrom; private final LocalDate recalculateFrom;
private final LocalDate lastChargeAppliedDate;
public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation, final LocalDate recalculateFrom) { public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation, final LocalDate recalculateFrom,
final LocalDate lastChargeAppliedDate) {
this.loan = loan; this.loan = loan;
this.runInterestRecalculation = runInterestRecalculation; this.runInterestRecalculation = runInterestRecalculation;
this.recalculateFrom = recalculateFrom; this.recalculateFrom = recalculateFrom;
this.lastChargeAppliedDate = lastChargeAppliedDate;
} }
public boolean isRunInterestRecalculation() { public boolean isRunInterestRecalculation() {
@@ -28,8 +31,12 @@ public class LoanOverdueDTO {
return this.loan; return this.loan;
} }
public LocalDate getRecalculateFrom() { public LocalDate getRecalculateFrom() {
return this.recalculateFrom; return this.recalculateFrom;
} }
public LocalDate getLastChargeAppliedDate() {
return this.lastChargeAppliedDate;
}
} }