MIFOSX-1421 : loan recalculation changes

This commit is contained in:
Pramod Nuthakki
2014-08-06 02:10:08 +05:30
parent 817794b26b
commit fd3a401821
28 changed files with 1415 additions and 160 deletions
@@ -0,0 +1,7 @@
INSERT INTO `job` (`name`, `display_name`, `cron_expression`, `create_time`, `task_priority`, `scheduler_group`) VALUES ('Recalculate Interest For Loans', 'Recalculate Interest For Loans', '0 1 0 1/1 * ? *', now(), 4, 3);
UPDATE `job` SET `scheduler_group`=3 WHERE `name`='Update Non Performing Assets';
UPDATE `job` SET `task_priority`=3 WHERE `name`='Add Accrual Transactions';
UPDATE `job` SET `task_priority`=2 WHERE `name`='Add Periodic Accrual Transactions';
@@ -22,7 +22,8 @@ public enum JobName {
UPDATE_NPA("Update Non Performing Assets"), UPDATE_NPA("Update Non Performing Assets"),
UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"), UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"),
TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"), TRANSFER_INTEREST_TO_SAVINGS("Transfer Interest To Savings"),
ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"); ADD_PERIODIC_ACCRUAL_ENTRIES("Add Periodic Accrual Transactions"),
RECALCULATE_INTEREST_FOR_LOAN("Recalculate Interest For Loans");
private final String name; private final String name;
@@ -32,8 +32,8 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
*/ */
CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId); CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId);
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId") @Query("from CalendarInstance ci where ci.entityId = :entityId and ci.entityTypeId = :entityTypeId")
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId); CalendarInstance findCalendarInstaneByEntityId(@Param("entityId") Long entityId, @Param("entityTypeId") Integer entityTypeId);
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId); Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
@@ -90,12 +90,13 @@ public class LoanTransactionsApiResource {
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails(); transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
} else if (is(commandParam, "disburse")) { } else if (is(commandParam, "disburse")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true); transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
} else if(is(commandParam, "disburseToSavings")){ } else if (is(commandParam, "disburseToSavings")) {
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false); transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
} else if(is(commandParam, "recoverypayment")){ } else if (is(commandParam, "recoverypayment")) {
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId); transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
} } else if (is(commandParam, "prepayLoan")) {
else { transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId);
} else {
throw new UnrecognizedQueryParamException("command", commandParam); throw new UnrecognizedQueryParamException("command", commandParam);
} }
@@ -150,7 +151,7 @@ public class LoanTransactionsApiResource {
} else if (is(commandParam, "undowriteoff")) { } else if (is(commandParam, "undowriteoff")) {
final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build(); final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
}else if(is(commandParam,"recoverypayment")){ } else if (is(commandParam, "recoverypayment")) {
final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build(); final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest); result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
} }
@@ -0,0 +1,33 @@
package org.mifosplatform.portfolio.loanaccount.data;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
public class LoanChargePaidDetail {
private final Money amount;
private final LoanRepaymentScheduleInstallment installment;
private final boolean isFeeCharge;
public LoanChargePaidDetail( Money amount,LoanRepaymentScheduleInstallment installment,boolean isFeeCharge){
this.amount = amount;
this.installment = installment;
this.isFeeCharge = isFeeCharge;
}
public Money getAmount() {
return this.amount;
}
public LoanRepaymentScheduleInstallment getInstallment() {
return this.installment;
}
public boolean isFeeCharge() {
return this.isFeeCharge;
}
}
@@ -60,6 +60,7 @@ import org.mifosplatform.organisation.staff.domain.Staff;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.accountdetails.domain.AccountType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType; import org.mifosplatform.portfolio.charge.domain.ChargeCalculationType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException; import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
@@ -310,6 +311,9 @@ public class Loan extends AbstractPersistable<Long> {
@OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true) @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true)
private LoanInterestRecalculationDetails loanInterestRecalculationDetails; private LoanInterestRecalculationDetails loanInterestRecalculationDetails;
@Column(name = "is_npa", nullable = false)
private boolean isNpa;
public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType,
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanTransactionProcessingStrategy transactionProcessingStrategy,
@@ -496,8 +500,8 @@ public class Loan extends AbstractPersistable<Long> {
// NOTE: must add new loan charge to set of loan charges before // NOTE: must add new loan charge to set of loan charges before
// reporcessing the repayment schedule. // reporcessing the repayment schedule.
if(this.charges == null) { if (this.charges == null) {
this.charges = new HashSet<>(); this.charges = new HashSet<>();
} }
this.charges.add(loanCharge); this.charges.add(loanCharge);
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1511,7 +1515,8 @@ public class Loan extends AbstractPersistable<Long> {
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
/** /**
* Copy interest recalculation settings if interest recalculation is enabled * Copy interest recalculation settings if interest recalculation is
* enabled
*/ */
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings(); this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings();
@@ -1722,7 +1727,8 @@ public class Loan extends AbstractPersistable<Long> {
final ApplicationCurrency currency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final ApplicationCurrency currency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday,
final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule) { final boolean allowTransactionsOnNonWorkingDay, final boolean recalculateSchedule,
final CalendarInstance calendarInstanceForInterestRecalculation) {
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
LoanStatus.fromInt(this.loanStatus)); LoanStatus.fromInt(this.loanStatus));
@@ -1765,6 +1771,11 @@ public class Loan extends AbstractPersistable<Long> {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
} }
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
handleDisbursementTransaction(actualDisbursementDate); handleDisbursementTransaction(actualDisbursementDate);
@@ -2127,7 +2138,8 @@ public class Loan extends AbstractPersistable<Long> {
} }
boolean isEmiAmountChanged = this.loanTermVariations.size() > 0; boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
updateLoanToPreDisbursalState(); updateLoanToPreDisbursalState();
if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged) { if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged
|| this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
// clear off actual disbusrement date so schedule regeneration // clear off actual disbusrement date so schedule regeneration
// uses expected date. // uses expected date.
@@ -2186,7 +2198,9 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction, public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) { final List<Long> existingReversedTransactionIds, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER); validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
@@ -2197,7 +2211,8 @@ public class Loan extends AbstractPersistable<Long> {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
loanLifecycleStateMachine, null); loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -2205,7 +2220,9 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, boolean isRecoveryRepayment) { final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final boolean isHolidayEnabled,
boolean isRecoveryRepayment, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
LoanEvent event = null; LoanEvent event = null;
if (isRecoveryRepayment) { if (isRecoveryRepayment) {
@@ -2224,7 +2241,8 @@ public class Loan extends AbstractPersistable<Long> {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
loanLifecycleStateMachine, null); loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -2289,7 +2307,10 @@ public class Loan extends AbstractPersistable<Long> {
} }
private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction, private ChangedTransactionDetail handleRepaymentOrRecoveryOrWaiverTransaction(final LoanTransaction loanTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
@@ -2353,10 +2374,36 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
if (isTransactionChronologicallyLatest && adjustedTransaction == null) {
final LoanRepaymentScheduleInstallment currentInstallment = fetchLoanRepaymentScheduleInstallment(loanTransaction
.getTransactionDate());
boolean reprocess = true;
if (adjustedTransaction == null && loanTransaction.getTransactionDate().isEqual(LocalDate.now()) && currentInstallment != null) {
reprocess = false;
}
if (isTransactionChronologicallyLatest && adjustedTransaction == null
&& (!reprocess || !this.repaymentScheduleDetail().isInterestRecalculationEnabled())) {
loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, loanRepaymentScheduleTransactionProcessor.handleTransaction(loanTransaction, getCurrency(), this.repaymentScheduleInstallments,
charges()); charges());
} else { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (currentInstallment == null || currentInstallment.isNotFullyPaidOff()) {
reprocess = true;
} else {
final LoanRepaymentScheduleInstallment nextInstallment = fetchRepaymentScheduleInstallment(currentInstallment
.getInstallmentNumber() + 1);
if (nextInstallment != null && nextInstallment.getTotalPaidInAdvance(getCurrency()).isGreaterThanZero()) {
reprocess = true;
}
}
}
}
if (reprocess) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
@@ -2384,6 +2431,17 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail; return changedTransactionDetail;
} }
private LoanRepaymentScheduleInstallment fetchLoanRepaymentScheduleInstallment(LocalDate dueDate) {
LoanRepaymentScheduleInstallment installment = null;
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
if (dueDate.equals(loanRepaymentScheduleInstallment.getDueDate())) {
installment = loanRepaymentScheduleInstallment;
break;
}
}
return installment;
}
private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() { private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() {
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>(); final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) { for (final LoanTransaction transaction : this.loanTransactions) {
@@ -2556,7 +2614,9 @@ public class Loan extends AbstractPersistable<Long> {
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay) { final boolean isHolidayEnabled, final boolean allowTransactionsOnNonWorkingDay,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
@@ -2589,13 +2649,17 @@ public class Loan extends AbstractPersistable<Long> {
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) { if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine, changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment); transactionForAdjustment, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
} }
return changedTransactionDetail; return changedTransactionDetail;
} }
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) { public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
@@ -2606,6 +2670,10 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges()); charges());
@@ -2672,12 +2740,13 @@ public class Loan extends AbstractPersistable<Long> {
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final AppUser currentUser, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final AppUser currentUser, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) { final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor); calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
calendarInstanceForInterestRecalculation);
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
@@ -2739,14 +2808,18 @@ public class Loan extends AbstractPersistable<Long> {
private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory, private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<Holiday> holidays, final WorkingDays workingDays,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final CalendarInstance calendarInstanceForInterestRecalculation) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
if (isDisbursementAllowed() && atleastOnceDisbursed()) { if (isDisbursementAllowed() && atleastOnceDisbursed()) {
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount()); this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
removeDisbursementDetail(); removeDisbursementDetail();
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays); workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
@@ -2776,7 +2849,7 @@ public class Loan extends AbstractPersistable<Long> {
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) { final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateAccountStatus(LoanEvent.LOAN_CLOSED); validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -2804,7 +2877,8 @@ public class Loan extends AbstractPersistable<Long> {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency, ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor); calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
calendarInstanceForInterestRecalculation);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
if (isOpen()) { if (isOpen()) {
@@ -3829,7 +3903,7 @@ public class Loan extends AbstractPersistable<Long> {
final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds, final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final LoanScheduleGeneratorFactory loanScheduleFactory, final Map<String, Object> actualChanges, final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) { final List<Holiday> holidays, final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE); validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName); final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate()); disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate());
@@ -3842,6 +3916,11 @@ public class Loan extends AbstractPersistable<Long> {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays); workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
@@ -3916,4 +3995,157 @@ public class Loan extends AbstractPersistable<Long> {
} }
return maturityDate; return maturityDate;
} }
public ChangedTransactionDetail recalculateSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
}
/***
* Commented since throwing exception if external id present for one of
* the transactions. for this need to save the reversed transactions
* first and then new transactions.
*/
// this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
}
private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return; }
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final List<DisbursementData> disbursementData = new ArrayList<>();
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
boolean isDefaultEmiAmountReq = true;
for (LoanTermVariations variationTerms : this.loanTermVariations) {
if (variationTerms.getTermType().isEMIAmountVariation()) {
if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) {
isDefaultEmiAmountReq = false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if (isDefaultEmiAmountReq) {
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(), this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate(), this.loanInterestRecalculationDetails,
calendarInstanceForInterestRecalculation);
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency,
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, retreiveListOfTransactionsPostDisbursement(),
loanRepaymentScheduleTransactionProcessor);
updateLoanSchedule(loanSchedule);
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge);
loanCharge.updateWaivedAmount(getCurrency());
} else {
loanCharge.setActive(false);
}
}
}
updateLoanSummaryDerivedFields();
}
public Money fetchPrepaymentDetail(final LoanScheduleGeneratorFactory loanScheduleFactory) {
Money amount = Money.zero(getCurrency());
if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) {
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final Integer loanTermFrequency = this.termFrequency;
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType);
final List<DisbursementData> disbursementData = new ArrayList<>();
for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) {
disbursementData.add(disbursementDetails.toData());
}
final List<LoanTermVariationsData> loanVariationTermsData = new ArrayList<>();
boolean isDefaultEmiAmountReq = true;
for (LoanTermVariations variationTerms : this.loanTermVariations) {
if (variationTerms.getTermType().isEMIAmountVariation()) {
if (variationTerms.getTermApplicableFrom().equals(this.getDisbursementDate().toDate())) {
isDefaultEmiAmountReq = false;
}
loanVariationTermsData.add(variationTerms.toData());
}
}
if (isDefaultEmiAmountReq) {
LoanTermVariationsData data = new LoanTermVariationsData(null,
LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), this.getDisbursementDate(),
this.fixedEmiAmount);
loanVariationTermsData.add(data);
}
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(null, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), null, getInArrearsTolerance(),
this.loanRepaymentScheduleDetail, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData,
this.maxOutstandingLoanBalance, loanVariationTermsData, getInterestChargedFromDate());
amount = loanScheduleGenerator.fetchPrepaymentAmount(this.repaymentScheduleInstallments, getCurrency(), loanApplicationTerms,
mc);
} else {
amount = this.getTotalOutstandingOnLoan();
}
return amount;
}
private Money getTotalOutstandingOnLoan() {
Money cumulativeOurstanding = Money.zero(loanCurrency());
for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) {
cumulativeOurstanding = cumulativeOurstanding.plus(scheduledRepayment.getTotalOutstanding(getCurrency()));
}
return cumulativeOurstanding;
}
} }
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
public interface LoanAccountDomainService { public interface LoanAccountDomainService {
@@ -27,4 +28,6 @@ public interface LoanAccountDomainService {
LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount, LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
PaymentDetail paymentDetail, String noteText, String txnExternalId); PaymentDetail paymentDetail, String noteText, String txnExternalId);
LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance);
} }
@@ -30,11 +30,18 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.account.domain.AccountTransferRepository; import org.mifosplatform.portfolio.account.domain.AccountTransferRepository;
import org.mifosplatform.portfolio.account.domain.AccountTransferTransaction; import org.mifosplatform.portfolio.account.domain.AccountTransferTransaction;
import org.mifosplatform.portfolio.calendar.domain.Calendar;
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.Client;
import org.mifosplatform.portfolio.client.exception.ClientNotActiveException; import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.exception.GroupNotActiveException; import org.mifosplatform.portfolio.group.exception.GroupNotActiveException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.note.domain.Note; import org.mifosplatform.portfolio.note.domain.Note;
import org.mifosplatform.portfolio.note.domain.NoteRepository; import org.mifosplatform.portfolio.note.domain.NoteRepository;
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail; import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
@@ -57,6 +64,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
private final JournalEntryWritePlatformService journalEntryWritePlatformService; private final JournalEntryWritePlatformService journalEntryWritePlatformService;
private final NoteRepository noteRepository; private final NoteRepository noteRepository;
private final AccountTransferRepository accountTransferRepository; private final AccountTransferRepository accountTransferRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
private final CalendarInstanceRepository calendarInstanceRepository;
@Autowired @Autowired
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository, public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
@@ -65,7 +75,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final WorkingDaysRepositoryWrapper workingDaysRepository, final WorkingDaysRepositoryWrapper workingDaysRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper, final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
final JournalEntryWritePlatformService journalEntryWritePlatformService, final JournalEntryWritePlatformService journalEntryWritePlatformService,
final AccountTransferRepository accountTransferRepository) { final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository) {
this.loanAccountAssembler = loanAccountAssembler; this.loanAccountAssembler = loanAccountAssembler;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -76,6 +88,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper; this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
this.journalEntryWritePlatformService = journalEntryWritePlatformService; this.journalEntryWritePlatformService = journalEntryWritePlatformService;
this.accountTransferRepository = accountTransferRepository; this.accountTransferRepository = accountTransferRepository;
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
} }
@Transactional @Transactional
@@ -118,9 +133,28 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
}
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
holidays, workingDays, allowTransactionsOnNonWorkingDay, isRecoveryRepayment); holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
try { try {
this.loanTransactionRepository.save(newRepaymentTransaction); this.loanTransactionRepository.save(newRepaymentTransaction);
@@ -324,6 +358,34 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
this.loanTransactionRepository.save(loanTransaction); this.loanTransactionRepository.save(loanTransaction);
} }
@Override
public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
final CalendarInstance calendarInstance) {
final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar();
LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
if (calendar != null) {// sync repayments
// TODO: AA - user provided first repayment date takes precedence
// over recalculated meeting date
if (calculatedRepaymentsStartingFromDate == null) {
// FIXME: AA - Possibility of having next meeting date
// immediately after disbursement date,
// need to have minimum number of days gap between disbursement
// and first repayment date.
final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail();
if (repaymentScheduleDetails != null) {// Not expecting to be
// null
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate,
repayEvery, frequency);
}
}
}
return calculatedRepaymentsStartingFromDate;
}
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) { if (transferTransaction != null) {
@@ -8,10 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.domain;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -41,6 +43,7 @@ import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType;
import org.mifosplatform.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException; import org.mifosplatform.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidDetail;
import org.springframework.data.jpa.domain.AbstractPersistable; import org.springframework.data.jpa.domain.AbstractPersistable;
@Entity @Entity
@@ -695,7 +698,12 @@ public class LoanCharge extends AbstractPersistable<Long> {
amountPaidToDate = amountPaidToDate.plus(amountOutstanding); amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = BigDecimal.ZERO; this.amountOutstanding = BigDecimal.ZERO;
this.paid = true; Money waivedAmount = Money.of(processAmount.getCurrency(), this.amountWaived);
if (waivedAmount.isGreaterThanZero()) {
this.waived = true;
} else {
this.paid = true;
}
} else { } else {
amountPaidOnThisCharge = processAmount; amountPaidOnThisCharge = processAmount;
@@ -800,14 +808,18 @@ public class LoanCharge extends AbstractPersistable<Long> {
return this.loanInstallmentCharge; return this.loanInstallmentCharge;
} }
public LoanRepaymentScheduleInstallment fetchRepaymentInstallment(final Money trasferedAmount) { public List<LoanChargePaidDetail> fetchRepaymentInstallment(final MonetaryCurrency currency) {
List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) { for (final LoanInstallmentCharge loanChargePerInstallment : this.loanInstallmentCharge) {
if (loanChargePerInstallment.isPending() if (loanChargePerInstallment.isPending()
&& trasferedAmount.getAmount().equals( && loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) {
loanChargePerInstallment.getAmountThroughChargePayment(trasferedAmount.getCurrency()).getAmount())) { return loanChargePerInstallment LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
.getRepaymentInstallment(); } loanChargePerInstallment.getAmountThroughChargePayment(currency),
loanChargePerInstallment.getRepaymentInstallment(), isFeeCharge());
chargePaidDetails.add(chargePaidDetail);
}
} }
return null; return chargePaidDetails;
} }
public boolean isActive() { public boolean isActive() {
@@ -842,4 +854,37 @@ public class LoanCharge extends AbstractPersistable<Long> {
public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge overdueInstallmentCharge) { public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge overdueInstallmentCharge) {
this.overdueInstallmentCharge = overdueInstallmentCharge; this.overdueInstallmentCharge = overdueInstallmentCharge;
} }
public void updateWaivedAmount(MonetaryCurrency currency) {
if (isInstalmentFee()) {
for (final LoanInstallmentCharge chargePerInstallment : this.loanInstallmentCharge) {
final Money amountWaived = chargePerInstallment.updateWaivedAmount(currency);
if (this.amountWaived == null) {
this.amountWaived = BigDecimal.ZERO;
}
this.amountWaived = this.amountWaived.add(amountWaived.getAmount());
this.amountOutstanding = this.amountOutstanding.subtract(amountWaived.getAmount());
if (determineIfFullyPaid()) {
this.paid = false;
this.waived = true;
}
return;
}
}
Money waivedAmount = Money.of(currency, this.amountWaived);
if (waivedAmount.isGreaterThanZero()) {
if (waivedAmount.isGreaterThan(this.getAmount(currency))) {
this.amountWaived = this.getAmount(currency).getAmount();
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
} else if (waivedAmount.isLessThan(this.getAmount(currency))) {
this.paid = false;
this.waived = false;
}
}
}
} }
@@ -153,7 +153,7 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid); Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding); final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
Money amountPaidPreviously = amountPaidToDate;
Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency()); Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) { if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
amountPaidOnThisCharge = amountOutstanding; amountPaidOnThisCharge = amountOutstanding;
@@ -166,8 +166,20 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
this.amountPaid = amountPaidToDate.getAmount(); this.amountPaid = amountPaidToDate.getAmount();
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency()); this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
} }
this.amountThroughChargePayment = feeAmount.getAmount(); Money amountFromChargePayment = Money.of(incrementBy.getCurrency(), this.amountThroughChargePayment);
this.paid = determineIfFullyPaid(); if (amountPaidPreviously.isGreaterThanZero()) {
amountFromChargePayment = amountFromChargePayment.plus(feeAmount);
} else {
amountFromChargePayment = feeAmount;
}
this.amountThroughChargePayment = amountFromChargePayment.getAmount();
if (determineIfFullyPaid()) {
if (this.amountWaived.compareTo(BigDecimal.ZERO) == 1) {
this.waived = true;
} else {
this.paid = true;
}
}
return amountPaidOnThisCharge; return amountPaidOnThisCharge;
} }
@@ -195,4 +207,20 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
return Money.of(currency, this.amountThroughChargePayment); return Money.of(currency, this.amountThroughChargePayment);
} }
public Money updateWaivedAmount(final MonetaryCurrency currency) {
if (this.amountWaived.compareTo(BigDecimal.ZERO) == 1) {
int comparedValue = this.amountWaived.compareTo(this.getAmount());
if (comparedValue == 1) {
this.amountWaived = this.getAmount();
this.amountOutstanding = BigDecimal.ZERO;
this.paid = false;
this.waived = true;
} else if (comparedValue == -1) {
this.paid = false;
this.waived = false;
}
}
return getAmountWaived(currency);
}
} }
@@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
@Table(name = "m_loan_recalculation_details") @Table(name = "m_loan_recalculation_details")
public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> { public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@OneToOne @OneToOne
@JoinColumn(name = "loan_id", nullable = false) @JoinColumn(name = "loan_id", nullable = false)
private Loan loan; private Loan loan;
@@ -65,4 +66,12 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable<Long>
this.rescheduleStrategyMethod = rescheduleStrategyMethod; this.rescheduleStrategyMethod = rescheduleStrategyMethod;
} }
public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod);
}
public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod);
}
} }
@@ -532,4 +532,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.fromDate = newFromDate.toDate(); this.fromDate = newFromDate.toDate();
} }
} }
public Money getTotalPaidInAdvance(final MonetaryCurrency currency) {
return Money.of(currency, this.totalPaidInAdvance);
}
public Money getTotalPaidLate(final MonetaryCurrency currency) {
return Money.of(currency, this.totalPaidLate);
}
} }
@@ -527,4 +527,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.externalId = externalId; this.externalId = externalId;
} }
public boolean isAccrual() {
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
}
} }
@@ -6,13 +6,16 @@
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidDetail;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargePaidBy; import org.mifosplatform.portfolio.loanaccount.domain.LoanChargePaidBy;
@@ -69,36 +72,47 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>(); final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>();
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) { for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
if (loanTransaction.isChargePayment()) { if (loanTransaction.isChargePayment()) {
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>(); List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid(); final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
final Set<LoanCharge> transferCharges = new HashSet<>(); final Set<LoanCharge> transferCharges = new HashSet<>();
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) { for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
LoanCharge loanCharge = chargePaidBy.getLoanCharge(); LoanCharge loanCharge = chargePaidBy.getLoanCharge();
transferCharges.add(loanCharge); transferCharges.add(loanCharge);
if (loanCharge.isInstalmentFee()) { if (loanCharge.isInstalmentFee()) {
LoanRepaymentScheduleInstallment installment = null; chargePaidDetails.addAll(loanCharge.fetchRepaymentInstallment(currency));
if (loanCharge.isFeeCharge()) {
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
} else {
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
}
if (installment != null) {
chargePaymentInstallments.add(installment);
}
} }
} }
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment installment : installments) { for (final LoanRepaymentScheduleInstallment installment : installments) {
for (final LoanCharge loanCharge : transferCharges) { for (final LoanCharge loanCharge : transferCharges) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) { if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
chargePaymentInstallments.add(installment); LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
installment.getFeeChargesOutstanding(currency), installment, loanCharge.isFeeCharge());
chargePaidDetails.add(chargePaidDetail);
break; break;
} }
} }
startDate = installment.getDueDate(); startDate = installment.getDueDate();
} }
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
handleTransaction(loanTransaction, currency, chargePaymentInstallments, transferCharges); Money unprocessed = loanTransaction.getAmount(currency);
for (LoanChargePaidDetail chargePaidDetail : chargePaidDetails) {
final List<LoanRepaymentScheduleInstallment> processInstallments = new ArrayList<>(1);
unprocessed = unprocessed.minus(chargePaidDetail.getAmount());
processInstallments.add(chargePaidDetail.getInstallment());
Money unprocessedAfterProcess = handleTransactionAndCharges(loanTransaction, currency, processInstallments,
transferCharges, chargePaidDetail.getAmount(), chargePaidDetail.isFeeCharge());
unprocessed = unprocessed.plus(unprocessedAfterProcess);
if (!unprocessed.isGreaterThanZero()) {
break;
}
}
if (unprocessed.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, unprocessed);
loanTransaction.updateOverPayments(unprocessed);
}
} else { } else {
transactionstoBeProcessed.add(loanTransaction); transactionstoBeProcessed.add(loanTransaction);
} }
@@ -152,12 +166,73 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency, public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) { final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
// find earliest unpaid installment for which to apply this transaction 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,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
final boolean isFeeCharge) {
// to. // to.
Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess);
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()) {
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (chargeAmountToProcess != null) {
if (isFeeCharge) {
feeCharges = chargeAmountToProcess;
} else {
penaltyCharges = chargeAmountToProcess;
}
}
if (feeCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
}
if (penaltyCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
}
}
return transactionAmountUnprocessed;
}
private Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, Money amountToProcess) {
int installmentIndex = 0; int installmentIndex = 0;
final LocalDate transactionDate = loanTransaction.getTransactionDate(); final LocalDate transactionDate = loanTransaction.getTransactionDate();
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency); Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
if (amountToProcess != null) {
transactionAmountUnprocessed = amountToProcess;
}
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) { for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
@@ -182,30 +257,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
installmentIndex++; installmentIndex++;
} }
return transactionAmountUnprocessed;
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()) {
final Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
if (feeCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, installmentNumber);
}
final Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
if (penaltyCharges.isGreaterThanZero()) {
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, installmentNumber);
}
}
if (transactionAmountUnprocessed.isGreaterThanZero()) {
onLoanOverpayment(loanTransaction, transactionAmountUnprocessed);
loanTransaction.updateOverPayments(transactionAmountUnprocessed);
}
} }
private Set<LoanCharge> extractFeeCharges(final Set<LoanCharge> loanCharges) { private Set<LoanCharge> extractFeeCharges(final Set<LoanCharge> loanCharges) {
@@ -367,4 +419,42 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) { protected void onLoanOverpayment(final LoanTransaction loanTransaction, final Money loanOverPaymentAmount) {
// empty implementation by default. // empty implementation by default.
} }
@Override
public Map<LocalDate, Money> handleRecalculation(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentinstallment,
final Map<LocalDate, LocalDate> recalculationDates) {
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
currentInstallment.resetDerivedComponents();
currentInstallment.updateDerivedFields(currency, disbursementDate);
}
return handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, currentinstallment, recalculationDates);
}
@Override
public Map<LocalDate, Money> handleRepaymentSchedule(final List<LoanTransaction> transactionsPostDisbursement,
final MonetaryCurrency currency, final List<LoanRepaymentScheduleInstallment> installments,
final LoanRepaymentScheduleInstallment installment, final Map<LocalDate, LocalDate> recalculationDates) {
Map<LocalDate, Money> unprocessedMap = new HashMap<>();
Money currentInstallmentOutstanding = installment.getTotalOutstanding(currency);
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
Money amountToProcess = null;
Money unProcessed = processTransaction(loanTransaction, currency, installments, amountToProcess);
if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate())
&& recalculationDates.get(loanTransaction.getTransactionDate()).isBefore(installment.getDueDate())) {
Money earlyPayment = currentInstallmentOutstanding.minus(installment.getTotalOutstanding(currency));
if (earlyPayment.isGreaterThanZero()) {
unprocessedMap.put(loanTransaction.getTransactionDate(), earlyPayment);
}
currentInstallmentOutstanding = installment.getTotalOutstanding(currency);
}
if (unProcessed.isGreaterThanZero()) {
unprocessedMap.put(loanTransaction.getTransactionDate(), unProcessed);
}
}
return unprocessedMap;
}
} }
@@ -6,10 +6,12 @@
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor; package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -26,4 +28,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
Map<LocalDate, Money> handleRecalculation(LocalDate disbursementDate, List<LoanTransaction> transactionsPostDisbursement,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentinstallment, Map<LocalDate, LocalDate> recalculationDates);
Map<LocalDate, Money> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment, Map<LocalDate, LocalDate> recalculationDates);
} }
@@ -39,8 +39,14 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isChargesWaiver()) { if (loanTransaction.isChargesWaiver()) {
// zero this type of transaction and ignore it for now. penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
transactionAmountRemaining = Money.zero(currency); loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) { } else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -64,8 +64,14 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isChargesWaiver()) { if (loanTransaction.isChargesWaiver()) {
// zero this type of transaction and ignore it for now. penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
transactionAmountRemaining = Money.zero(currency); loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) { } else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -78,8 +78,14 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isChargesWaiver()) { if (loanTransaction.isChargesWaiver()) {
// zero this type of transaction and ignore it for now. penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
transactionAmountRemaining = Money.zero(currency); loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) { } else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -64,8 +64,14 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency()); Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
if (loanTransaction.isChargesWaiver()) { if (loanTransaction.isChargesWaiver()) {
// zero this type of transaction and ignore it for now. penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
transactionAmountRemaining = Money.zero(currency); loanTransaction.getPenaltyChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
feeChargesPortion = currentInstallment
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
} else if (loanTransaction.isInterestWaiver()) { } else if (loanTransaction.isInterestWaiver()) {
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining); interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion); transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
@@ -9,8 +9,11 @@ import java.math.BigDecimal;
import java.math.MathContext; import java.math.MathContext;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
@@ -19,8 +22,13 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementDisbursementDateException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
@@ -37,6 +45,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) { final List<Holiday> holidays, final WorkingDays workingDays) {
final List<RecalculationDetail> diffAmt = null;
return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt);
}
private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, List<RecalculationDetail> diffAmt) {
// 1. generate list of proposed schedule due dates // 1. generate list of proposed schedule due dates
final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
@@ -66,7 +81,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments,
loanApplicationTerms, chargesDueAtTimeOfDisbursement); loanApplicationTerms, chargesDueAtTimeOfDisbursement);
// 4. Determine the total interest owed over the full loan for FLAT interest method . // 4. Determine the total interest owed over the full loan for FLAT
// interest method .
Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged( Money totalInterestChargedForFullLoanTerm = loanApplicationTerms.calculateTotalInterestCharged(
this.paymentPeriodsInOneYearCalculator, mc); this.paymentPeriodsInOneYearCalculator, mc);
@@ -91,10 +107,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt)); loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
outstandingBalance = outstandingBalance.zero().plus(disburseAmt); outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
} }
Money reducePrincipal = totalCumulativePrincipal.zero();
Money fixedEmiAmount = totalCumulativePrincipal.zero();
while (!outstandingBalance.isZero()) { while (!outstandingBalance.isZero()) {
isFirstRepayment = false;
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isFirstRepayment); isFirstRepayment);
isFirstRepayment = false;
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays); isHolidayEnabled, holidays, workingDays);
final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
@@ -127,12 +145,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate,
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery()); loanApplicationTerms.getRepaymentEvery());
Money balanceForcalculation = outstandingBalance;
if (reducePrincipal.isGreaterThanZero()) {
switch (loanApplicationTerms.getRescheduleStrategyMethod()) {
case REDUCE_EMI_AMOUNT:
loanApplicationTerms.setFixedEmiAmount(null);
outstandingBalance = outstandingBalance.minus(reducePrincipal);
balanceForcalculation = outstandingBalance;
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
reducePrincipal = reducePrincipal.zero();
break;
case REDUCE_NUMBER_OF_INSTALLMENTS:
loanApplicationTerms.setFixedEmiAmount(fixedEmiAmount.getAmount());
outstandingBalance = outstandingBalance.minus(reducePrincipal);
balanceForcalculation = outstandingBalance;
totalCumulativePrincipal = totalCumulativePrincipal.plus(reducePrincipal);
reducePrincipal = reducePrincipal.zero();
break;
case RESCHEDULE_NEXT_REPAYMENTS:
balanceForcalculation = balanceForcalculation.minus(reducePrincipal);
break;
default:
break;
}
}
if (!balanceForcalculation.isGreaterThanZero()) {
break;
}
// 5 determine principal,interest of repayment period // 5 determine principal,interest of repayment period
final PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( final PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal, this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace, totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
daysInPeriodApplicableForInterest, outstandingBalance, loanApplicationTerms, periodNumber, mc); daysInPeriodApplicableForInterest, balanceForcalculation, loanApplicationTerms, periodNumber, mc);
if (loanApplicationTerms.getFixedEmiAmount() != null if (loanApplicationTerms.getFixedEmiAmount() != null
&& loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) { && loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) {
@@ -141,32 +187,102 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
loanApplicationTerms.getFixedEmiAmount()); loanApplicationTerms.getFixedEmiAmount());
} }
// update cumulative fields for principal & interest // update cumulative fields for principal & interest
Money interestForThisinstallment = principalInterestForThisPeriod.interest();
totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal()); totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal());
totalCumulativeInterest = totalCumulativeInterest.plus(principalInterestForThisPeriod.interest()); totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment);
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace(); totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
// 6. update outstandingLoanBlance using correct 'principalDue' // 6. update outstandingLoanBlance using correct 'principalDue'
outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal()); outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal());
Money actualOutstandingbalance = outstandingBalance;
Money feeChargesForInstallment = principalDisbursed.zero(); Money feeChargesForInstallment = principalDisbursed.zero();
Money penaltyChargesForInstallment = principalDisbursed.zero(); Money penaltyChargesForInstallment = principalDisbursed.zero();
Money principalForThisPeriod = principalInterestForThisPeriod.principal();
if (principalForThisPeriod.isGreaterThan(reducePrincipal)) {
principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal);
reducePrincipal = reducePrincipal.zero();
} else {
reducePrincipal = reducePrincipal.minus(principalForThisPeriod);
principalForThisPeriod = principalForThisPeriod.zero();
}
if (periodNumber == 1) {
fixedEmiAmount = principalForThisPeriod.plus(interestForThisinstallment);
}
if (diffAmt != null && !diffAmt.isEmpty()) {
Money interestDueToLatePayment = totalCumulativeInterest.zero();
Money interestReducedDueToEarlyPayment = totalCumulativeInterest.zero();
for (RecalculationDetail detail : diffAmt) {
if (!detail.isLatePayment() && detail.getStartDate().isAfter(periodStartDate)
&& !detail.getStartDate().isAfter(scheduledDueDate)) {
reducePrincipal = reducePrincipal.plus(detail.getAmount());
int diffDays = Days.daysBetween(detail.getStartDate(), scheduledDueDate).getDays();
if (diffDays > 0) {
reducePrincipal = reducePrincipal.minus(principalForThisPeriod);
}
if (reducePrincipal.isLessThanZero()) {
reducePrincipal = reducePrincipal.zero();
} else {
Money actualOutstanding = outstandingBalance.minus(reducePrincipal);
principalForThisPeriod = reducePrincipal.plus(principalForThisPeriod);
if (actualOutstanding.isLessThanZero()) {
principalForThisPeriod = principalForThisPeriod.plus(actualOutstanding);
}
}
interestReducedDueToEarlyPayment = interestReducedDueToEarlyPayment.plus(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount()));
} else if (detail.isLatePayment() && detail.isOverlapping(periodStartDate, scheduledDueDate)
&& periodStartDate.isBefore(LocalDate.now())) {
LocalDate fromDate = periodStartDate;
LocalDate toDate = scheduledDueDate;
if (!detail.getStartDate().isAfter(periodStartDate)) {
fromDate = detail.getStartDate();
}
if (!detail.getToDate().isAfter(scheduledDueDate)) {
toDate = detail.getToDate();
}
if (toDate.isAfter(LocalDate.now())) {
toDate = LocalDate.now();
}
int diffDays = Days.daysBetween(fromDate, toDate).getDays();
interestDueToLatePayment = interestDueToLatePayment.plus(loanApplicationTerms.interestRateFor(
this.paymentPeriodsInOneYearCalculator, mc, diffDays, detail.getAmount()));
}
}
if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) {
totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.plus(interestDueToLatePayment)
.minus(interestReducedDueToEarlyPayment);
} else {
totalCumulativeInterest = totalCumulativeInterest.plus(interestDueToLatePayment)
.minus(interestReducedDueToEarlyPayment);
interestForThisinstallment = interestForThisinstallment.plus(interestDueToLatePayment).minus(
interestReducedDueToEarlyPayment);
}
}
actualOutstandingbalance = actualOutstandingbalance.minus(reducePrincipal);
// 8. sum up real totalInstallmentDue from components // 8. sum up real totalInstallmentDue from components
final Money totalInstallmentDue = principalInterestForThisPeriod.principal() // final Money totalInstallmentDue = principalForThisPeriod//
.plus(principalInterestForThisPeriod.interest()) // .plus(interestForThisinstallment) //
.plus(feeChargesForInstallment) // .plus(feeChargesForInstallment) //
.plus(penaltyChargesForInstallment); .plus(penaltyChargesForInstallment);
// 9. create repayment period from parts // 9. create repayment period from parts
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, periodStartDate, final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, periodStartDate,
scheduledDueDate, principalInterestForThisPeriod.principal(), outstandingBalance, scheduledDueDate, principalForThisPeriod, actualOutstandingbalance, interestForThisinstallment,
principalInterestForThisPeriod.interest(), feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue); feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue);
periods.add(installment); periods.add(installment);
// handle cumulative fields // handle cumulative fields
loanTermInDays += daysInPeriod; loanTermInDays += daysInPeriod;
totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount()); totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount());
totalInterestCharged = totalInterestCharged.add(principalInterestForThisPeriod.interest().getAmount()); totalInterestCharged = totalInterestCharged.add(interestForThisinstallment.getAmount());
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount()); totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
@@ -198,6 +314,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
} }
if (diffAmt != null && !diffAmt.isEmpty() && !periodStartDate.isAfter(LocalDate.now())) {
Map<LocalDate, RecalculationDetail> processDetails = new TreeMap<>();
for (RecalculationDetail detail : diffAmt) {
if (periodStartDate.isBefore(detail.getStartDate()) && detail.isLatePayment()) {
if (processDetails.containsKey(detail.getToDate())) {
RecalculationDetail recalculationDetail = processDetails.get(detail.getToDate());
RecalculationDetail updatedDetail = new RecalculationDetail(recalculationDetail.isLatePayment(),
recalculationDetail.getStartDate(), recalculationDetail.getToDate(), recalculationDetail.getAmount().plus(
detail.getAmount()));
processDetails.put(updatedDetail.getToDate(), updatedDetail);
} else {
processDetails.put(detail.getToDate(), detail);
}
}
}
for (RecalculationDetail detail : processDetails.values()) {
LocalDate fromDate = detail.getStartDate();
LocalDate toDate = detail.getToDate();
int diffDays = Days.daysBetween(fromDate, toDate).getDays();
Money interestDueToLatePayment = loanApplicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, diffDays,
detail.getAmount());
totalInterestCharged = totalInterestCharged.add(interestDueToLatePayment.getAmount());
totalRepaymentExpected = totalRepaymentExpected.add(interestDueToLatePayment.getAmount());
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, fromDate, toDate,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment,
interestDueToLatePayment.zero(), interestDueToLatePayment.zero(), interestDueToLatePayment);
periods.add(installment);
periodNumber++;
}
}
return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected, return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed, totalPrincipalExpected,
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
totalOutstanding); totalOutstanding);
@@ -366,4 +516,205 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
return cumulative; return cumulative;
} }
@Override
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
int installmentNumber = 0;
final List<RecalculationDetail> recalculationDetails = new ArrayList<>();
LoanScheduleModel loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled,
holidays, workingDays);
while (installmentNumber < loanScheduleModel.getPeriods().size() - 1) {
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, installmentNumber,
loanScheduleModel, recalculationDetails);
installmentNumber = recalculatedSchedule.getInstallmentNumber();
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
}
return loanScheduleModel;
}
private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
LoanScheduleModel model, final List<RecalculationDetail> recalculationDetails) {
boolean processRecalculate = false;
int processedInstallmentNumber = installmentNumber;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final List<RecalculationDetail> diffAmt = new ArrayList<>();
Money unpaidPricipal = loanApplicationTerms.getPrincipal();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
if (scheduledLoanInstallment.isRepaymentPeriod()) {
final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
scheduledLoanInstallment.penaltyChargesDue());
installments.add(installment);
}
}
MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
final List<LoanRepaymentScheduleInstallment> processinstallmets = new ArrayList<>();
for (LoanRepaymentScheduleInstallment installment : installments) {
processinstallmets.add(installment);
unpaidPricipal = unpaidPricipal.minus(installment.getPrincipal(currency));
if (installment.getInstallmentNumber() <= installmentNumber) {
continue;
}
processedInstallmentNumber = installment.getInstallmentNumber();
if (installment.getDueDate().isAfter(LocalDate.now())) {
continue;
}
List<LoanTransaction> transactionsForInstallment = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
for (LoanTransaction loanTransaction : transactions) {
LocalDate loantransactionDate = loanTransaction.getTransactionDate();
if (!loantransactionDate.isAfter(transactionsDate) && !loanTransaction.isAccrual()) {
transactionsForInstallment.add(loanTransaction);
recalculationDates.put(
loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
}
}
Map<LocalDate, Money> earlyPaymentMap = loanRepaymentScheduleTransactionProcessor.handleRecalculation(
loanApplicationTerms.getExpectedDisbursementDate(), transactionsForInstallment, currency, processinstallmets,
installment, recalculationDates);
for (Map.Entry<LocalDate, Money> entry : earlyPaymentMap.entrySet()) {
LocalDate startDate = entry.getKey();
if (unpaidPricipal.isGreaterThan(entry.getValue())) {
startDate = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
}
RecalculationDetail recalculationDetail = new RecalculationDetail(false, startDate, null, entry.getValue());
if (!entry.getKey().isAfter(installment.getDueDate()) && entry.getKey().isAfter(installment.getFromDate())) {
diffAmt.add(recalculationDetail);
}
}
if (installment.getDueDate().isBefore(LocalDate.now())) {
LocalDate startDate = installment.getDueDate();
Money totalOutstanding = installment.getTotalOutstanding(currency);
while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) {
LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency,
loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
totalOutstanding = installment.getTotalOutstanding(currency);
if (totalOutstanding.isGreaterThanZero()) {
Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency);
switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) {
case INTEREST:
latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency));
break;
case INTEREST_AND_FEE:
latepaymentoutstanding = latepaymentoutstanding.plus(installment.getInterestOutstanding(currency))
.plus(installment.getFeeChargesOutstanding(currency))
.plus(installment.getPenaltyChargesOutstanding(currency));
break;
case FEE:
latepaymentoutstanding = latepaymentoutstanding.plus(installment.getFeeChargesOutstanding(currency)).plus(
installment.getPenaltyChargesOutstanding(currency));
break;
default:
break;
}
RecalculationDetail recalculationDetail = new RecalculationDetail(true, recalculateFrom, recalcualteTill,
latepaymentoutstanding);
diffAmt.add(recalculationDetail);
}
startDate = recalcualteTill;
}
}
if (!diffAmt.isEmpty()) {
processRecalculate = true;
break;
}
}
if (processRecalculate) {
recalculationDetails.addAll(diffAmt);
model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays,
recalculationDetails);
}
return new RecalculatedSchedule(model, processedInstallmentNumber);
}
private void applyRest(List<LoanTransaction> loanTransactions, LocalDate from, LocalDate to,
LoanRepaymentScheduleInstallment installment,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency,
final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
List<LoanTransaction> transactions = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
for (LoanTransaction transaction : loanTransactions) {
LocalDate loantransactionDate = transaction.getTransactionDate();
if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to) && !transaction.isAccrual()) {
transactions.add(transaction);
recalculationDates.put(
loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
}
}
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>(1);
installments.add(installment);
loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(transactions, currency, installments, installment,
recalculationDates);
}
@SuppressWarnings("unused")
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
/*
* nextScheduleDate =
* this.scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate,
* loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
*/
return nextScheduleDate;
}
@Override
public Money fetchPrepaymentAmount(final List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
final LoanApplicationTerms applicationTerms, final MathContext mc) {
Money prepaymentAmount = Money.zero(currency);
Money amount = Money.zero(currency);
LocalDate calculateInterestFrom = LocalDate.now();
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
if (currentInstallment.isNotFullyPaidOff()) {
if (!currentInstallment.getDueDate().isAfter(LocalDate.now())) {
amount = amount.plus(currentInstallment.getTotalOutstanding(currency));
} else {
prepaymentAmount = prepaymentAmount.plus(currentInstallment.getPrincipalOutstanding(currency));
if (currentInstallment.getFromDate().isBefore(calculateInterestFrom)) {
calculateInterestFrom = currentInstallment.getFromDate();
}
}
}
}
int days = Days.daysBetween(calculateInterestFrom, LocalDate.now()).getDays();
Money interest = applicationTerms.interestRateFor(this.paymentPeriodsInOneYearCalculator, mc, days, prepaymentAmount);
prepaymentAmount = prepaymentAmount.plus(interest).plus(amount);
return prepaymentAmount;
}
} }
@@ -16,15 +16,19 @@ import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.common.domain.DaysInMonthType; import org.mifosplatform.portfolio.common.domain.DaysInMonthType;
import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.DaysInYearType;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails;
import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod;
public final class LoanApplicationTerms { public final class LoanApplicationTerms {
@@ -108,7 +112,13 @@ public final class LoanApplicationTerms {
private final DaysInYearType daysInYearType; private final DaysInYearType daysInYearType;
private final boolean isInterestRecalculationEnabled; private final boolean interestRecalculationEnabled;
private final LoanRescheduleStrategyMethod rescheduleStrategyMethod;
private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod;
private final CalendarInstance restCalendarInstance;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -124,12 +134,16 @@ public final class LoanApplicationTerms {
final List<LoanTermVariationsData> emiAmountVariations, final Integer graceOnArrearsAgeing, final List<LoanTermVariationsData> emiAmountVariations, final Integer graceOnArrearsAgeing,
final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) { final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
final CalendarInstance restCalendarInstance = null;
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType,
annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate,
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas,
maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType,
isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance);
} }
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -160,6 +174,9 @@ public final class LoanApplicationTerms {
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
final CalendarInstance restCalendarInstance = null;
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
@@ -167,7 +184,54 @@ public final class LoanApplicationTerms {
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
daysInMonthType, daysInYearType, isInterestRecalculationEnabled); daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
interestRecalculationCompoundingMethod, restCalendarInstance);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final LocalDate expectedDisbursementDate,
final LocalDate repaymentsStartingFromDate, final LocalDate calculatedRepaymentsStartingFromDate,
final Money inArrearsTolerance, final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan,
final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas, final BigDecimal maxOutstandingBalance,
final List<LoanTermVariationsData> emiAmountVariations, final LocalDate interestChargedFromDate,
final LoanInterestRecalculationDetails interestRecalculationDetails, final CalendarInstance restCalendarInstance) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType();
final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod();
final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod();
final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod();
final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType();
final BigDecimal annualNominalInterestRate = loanProductRelatedDetail.getAnnualNominalInterestRate();
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail
.getInterestCalculationPeriodMethod();
final Money principalMoney = loanProductRelatedDetail.getPrincipal();
//
final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment();
final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment();
final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged();
// Interest recalculation settings
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
if (isInterestRecalculationEnabled) {
rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod();
interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod();
}
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney,
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(),
daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod,
interestRecalculationCompoundingMethod, restCalendarInstance);
} }
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
@@ -182,7 +246,8 @@ public final class LoanApplicationTerms {
final boolean multiDisburseLoan, final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas, final boolean multiDisburseLoan, final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas,
final BigDecimal maxOutstandingBalance, final List<LoanTermVariationsData> emiAmountVariations, final BigDecimal maxOutstandingBalance, final List<LoanTermVariationsData> emiAmountVariations,
final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType,
final boolean isInterestRecalculationEnabled) { final boolean isInterestRecalculationEnabled, final LoanRescheduleStrategyMethod rescheduleStrategyMethod,
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, final CalendarInstance restCalendarInstance) {
this.currency = currency; this.currency = currency;
this.loanTermFrequency = loanTermFrequency; this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -216,7 +281,10 @@ public final class LoanApplicationTerms {
this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.graceOnArrearsAgeing = graceOnArrearsAgeing;
this.daysInMonthType = daysInMonthType; this.daysInMonthType = daysInMonthType;
this.daysInYearType = daysInYearType; this.daysInYearType = daysInYearType;
this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; this.interestRecalculationEnabled = isInterestRecalculationEnabled;
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod;
this.restCalendarInstance = restCalendarInstance;
} }
public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate,
@@ -593,6 +661,18 @@ public final class LoanApplicationTerms {
return periodicInterestRate; return periodicInterestRate;
} }
public Money interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, int days,
final Money outstandingBalance) {
final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator);
final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0"));
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(
divisor, mc);
BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(days), mc);
return outstandingBalance.multiplyRetainScale(interestRate, mc.getRoundingMode());
}
private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) {
long periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue(); long periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue();
@@ -663,7 +743,7 @@ public final class LoanApplicationTerms {
private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) { private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) {
if (multiDisburseLoan && fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); } if (fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); }
final double futureValue = 0; final double futureValue = 0;
final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue(); final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue();
@@ -762,7 +842,7 @@ public final class LoanApplicationTerms {
this.interestCalculationPeriodMethod, this.repaymentEvery, this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.interestCalculationPeriodMethod, this.repaymentEvery, this.repaymentPeriodFrequencyType, this.numberOfRepayments,
this.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod, this.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod,
this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(),
this.daysInYearType.getValue(), this.isInterestRecalculationEnabled); this.daysInYearType.getValue(), this.interestRecalculationEnabled);
} }
public Integer getLoanTermFrequency() { public Integer getLoanTermFrequency() {
@@ -863,4 +943,28 @@ public final class LoanApplicationTerms {
} }
} }
} }
public void setFixedEmiAmount(BigDecimal fixedEmiAmount) {
this.fixedEmiAmount = fixedEmiAmount;
}
public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() {
return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT;
}
public boolean isInterestRecalculationEnabled() {
return this.interestRecalculationEnabled;
}
public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
return this.rescheduleStrategyMethod;
}
public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
return this.interestRecalculationCompoundingMethod;
}
public CalendarInstance getRestCalendarInstance() {
return this.restCalendarInstance;
}
} }
@@ -11,11 +11,24 @@ import java.util.Set;
import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms, LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays); Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays);
LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency,
LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays,
WorkingDays workingDays, List<LoanTransaction> transactions,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
Money fetchPrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
LoanApplicationTerms applicationTerms, MathContext mc);
} }
@@ -0,0 +1,20 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
public class RecalculatedSchedule {
private final LoanScheduleModel loanScheduleModel;
private final Integer installmentNumber;
public RecalculatedSchedule(final LoanScheduleModel loanScheduleModel, final Integer installmentNumber) {
this.loanScheduleModel = loanScheduleModel;
this.installmentNumber = installmentNumber;
}
public LoanScheduleModel getLoanScheduleModel() {
return this.loanScheduleModel;
}
public Integer getInstallmentNumber() {
return this.installmentNumber;
}
}
@@ -0,0 +1,44 @@
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.Money;
public class RecalculationDetail {
private boolean isLatePayment;
private LocalDate startDate;
private LocalDate toDate;
private Money amount;
public RecalculationDetail(final boolean isLatePayment, final LocalDate startDate, final LocalDate toDate, final Money amount) {
this.isLatePayment = isLatePayment;
this.startDate = startDate;
this.toDate = toDate;
this.amount = amount;
}
public boolean isLatePayment() {
return this.isLatePayment;
}
public LocalDate getStartDate() {
return this.startDate;
}
public LocalDate getToDate() {
return this.toDate;
}
public Money getAmount() {
return this.amount;
}
public boolean isOverlapping(LocalDate startDate, LocalDate endDate) {
return !this.startDate.isAfter(endDate) && !startDate.isAfter(this.toDate);
}
public void updateAmount(Money amount) {
this.amount = amount;
}
}
@@ -87,4 +87,8 @@ public interface LoanReadPlatformService {
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId); LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate); Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
Collection<Long> fetchArrearLoans();
LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId);
} }
@@ -81,6 +81,7 @@ import org.mifosplatform.portfolio.loanaccount.exception.LoanTransactionNotFound
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.mifosplatform.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData;
import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
@@ -120,6 +121,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
private final LoanMapper loaanLoanMapper = new LoanMapper(); private final LoanMapper loaanLoanMapper = new LoanMapper();
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd"); private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
private final LoanScheduleGeneratorFactory loanScheduleFactory;
@Autowired @Autowired
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
@@ -129,7 +131,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService, final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService,
final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService, final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService,
final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource, final CodeValueReadPlatformService codeValueReadPlatformService, final RoutingDataSource dataSource,
final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService) { final CalendarReadPlatformService calendarReadPlatformService, final StaffReadPlatformService staffReadPlatformService,
final LoanScheduleGeneratorFactory loanScheduleFactory) {
this.context = context; this.context = context;
this.loanRepository = loanRepository; this.loanRepository = loanRepository;
this.loanTransactionRepository = loanTransactionRepository; this.loanTransactionRepository = loanTransactionRepository;
@@ -145,6 +148,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
this.staffReadPlatformService = staffReadPlatformService; this.staffReadPlatformService = staffReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource); this.jdbcTemplate = new JdbcTemplate(dataSource);
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource); this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
this.loanScheduleFactory = loanScheduleFactory;
} }
@Override @Override
@@ -364,6 +368,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, paymentOptions, null, null, null); loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency).getAmount(), null, paymentOptions, null, null, null);
} }
@Override
public LoanTransactionData retrieveLoanPrePaymentTemplate(final Long loanId) {
this.context.authenticatedUser();
final Loan loan = this.loanRepository.findOne(loanId);
if (loan == null) { throw new LoanNotFoundException(loanId); }
final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CurrencyData currencyData = applicationCurrency.toData();
final LocalDate earliestUnpaidInstallmentDate = LocalDate.now();
final Money precloseAmount = loan.fetchPrepaymentDetail(this.loanScheduleFactory);
final LoanTransactionEnumData transactionType = LoanEnumerations.transactionType(LoanTransactionType.REPAYMENT);
final Collection<CodeValueData> paymentOptions = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(PaymentDetailConstants.paymentTypeCodeName);
return new LoanTransactionData(null, null, null, transactionType, null, currencyData, earliestUnpaidInstallmentDate,
precloseAmount.getAmount(), null, null, null, null, null, paymentOptions, null, null, null);
}
@Override @Override
public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) { public LoanTransactionData retrieveWaiveInterestDetails(final Long loanId) {
@@ -1533,4 +1560,22 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
loan.getTotalOutstandingAmount(), null, null, null, null, null, null, null, null); loan.getTotalOutstandingAmount(), null, null, null, null, null, null, null, null);
} }
@Override
public Collection<Long> fetchArrearLoans() {
StringBuilder sqlBuilder = new StringBuilder();
sqlBuilder.append("SELECT ml.id FROM m_loan ml ");
sqlBuilder.append(" INNER JOIN m_loan_repayment_schedule mr on mr.loan_id = ml.id ");
sqlBuilder.append(" WHERE ml.loan_status_id = ? ");
sqlBuilder.append(" and ml.interest_recalculation_enabled = 1 ");
sqlBuilder.append(" and ml.is_npa = 0 ");
sqlBuilder.append(" and mr.completed_derived is false ");
sqlBuilder.append(" and mr.duedate < ? ");
sqlBuilder.append(" group by ml.id");
try {
return this.jdbcTemplate.queryForList(sqlBuilder.toString(), Long.class, new Object[] { LoanStatus.ACTIVE.getValue(), formatter.print(LocalDate.now()) });
} catch (final EmptyResultDataAccessException e) {
return null;
}
}
} }
@@ -79,4 +79,6 @@ public interface LoanWritePlatformService {
CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command); CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command);
void recalculateInterest();
} }
@@ -61,7 +61,6 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.domain.CalendarType;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.Charge;
import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode;
import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper; import org.mifosplatform.portfolio.charge.domain.ChargeRepositoryWrapper;
@@ -115,7 +114,6 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDate
import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator; import org.mifosplatform.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer; import org.mifosplatform.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException; import org.mifosplatform.portfolio.loanproduct.exception.InvalidCurrencyException;
import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
@@ -237,7 +235,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkForProductMixRestrictions(loan); checkForProductMixRestrictions(loan);
// validate actual disbursement date against meeting date // validate actual disbursement date against meeting date
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
if (loan.isSyncDisbursementWithMeeting()) { if (loan.isSyncDisbursementWithMeeting()) {
@@ -257,8 +255,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// Recalculate first repayment date based in actual disbursement date. // Recalculate first repayment date based in actual disbursement date.
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
calendarInstance); actualDisbursementDate, loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
actualDisbursementDate.toDate()); actualDisbursementDate.toDate());
@@ -282,9 +280,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getLoanTransactions().add(disbursementTransaction); loan.getLoanTransactions().add(disbursementTransaction);
} }
CalendarInstance restCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule); allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -376,33 +380,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
private LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan,
final CalendarInstance calendarInstance) {
final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar();
LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate();
if (calendar != null) {// sync repayments
// TODO: AA - user provided first repayment date takes precedence
// over recalculated meeting date
if (calculatedRepaymentsStartingFromDate == null) {
// FIXME: AA - Possibility of having next meeting date
// immediately after disbursement date,
// need to have minimum number of days gap between disbursement
// and first repayment date.
final LoanProductRelatedDetail repaymentScheduleDetails = loan.repaymentScheduleDetail();
if (repaymentScheduleDetails != null) {// Not expecting to be
// null
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate,
repayEvery, frequency);
}
}
}
return calculatedRepaymentsStartingFromDate;
}
/**** /****
* TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base * TODO Vishwas: Pair with Ashok and re-factor collection sheet code-base
* *
@@ -462,9 +439,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
disbursementTransaction.updateLoan(loan); disbursementTransaction.updateLoan(loan);
loan.getLoanTransactions().add(disbursementTransaction); loan.getLoanTransactions().add(disbursementTransaction);
} }
CalendarInstance restCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds, loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule); allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
try { try {
@@ -541,11 +524,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final MonetaryCurrency currency = loan.getCurrency(); final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
final LocalDate actualDisbursementDate = loan.getDisbursementDate(); final LocalDate actualDisbursementDate = loan.getDisbursementDate();
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan, final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
calendarInstance); actualDisbursementDate, loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
@@ -699,9 +682,28 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled(); final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
}
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay); allowTransactionsOnHoliday, holidays, workingDays, isHolidayEnabled, allowTransactionsOnNonWorkingDay,
this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
if (paymentDetail != null) { if (paymentDetail != null) {
@@ -783,8 +785,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
transactionDate); transactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction, CalendarInstance restCalendarInstance = null;
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds); ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
final ChangedTransactionDetail changedTransactionDetail = loan
.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
this.loanTransactionRepository.save(waiveInterestTransaction); this.loanTransactionRepository.save(waiveInterestTransaction);
@@ -844,10 +870,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate()); .getDisbursementDate().toDate());
@@ -857,9 +883,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final MonetaryCurrency currency = loan.getCurrency(); final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CalendarInstance restCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(),
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory, changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L); LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
this.loanTransactionRepository.save(writeoff); this.loanTransactionRepository.save(writeoff);
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
@@ -906,10 +938,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate()); .getDisbursementDate().toDate());
@@ -919,9 +951,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final MonetaryCurrency currency = loan.getCurrency(); final MonetaryCurrency currency = loan.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CalendarInstance restCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L); final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
if (possibleClosingTransaction != null) { if (possibleClosingTransaction != null) {
this.loanTransactionRepository.save(possibleClosingTransaction); this.loanTransactionRepository.save(possibleClosingTransaction);
@@ -1429,9 +1467,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate); final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate);
loan.getLoanTransactions().add(newTransferAcceptanceTransaction); loan.getLoanTransactions().add(newTransferAcceptanceTransaction);
if(loan.getTotalOverpaid() !=null){ if (loan.getTotalOverpaid() != null) {
loan.setLoanStatus(LoanStatus.OVERPAID.getValue()); loan.setLoanStatus(LoanStatus.OVERPAID.getValue());
}else{ } else {
loan.setLoanStatus(LoanStatus.ACTIVE.getValue()); loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
} }
if (loanOfficer != null) { if (loanOfficer != null) {
@@ -1953,7 +1991,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException( if (!loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException(
"error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + loanId "error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + loanId
+ " update not allowed as loan status is not written off", loanId); } + " update not allowed as loan status is not written off", loanId); }
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds); CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds,
isHolidayEnabled, holidays, workingDays, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
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()) {
this.loanTransactionRepository.save(mapEntry.getValue()); this.loanTransactionRepository.save(mapEntry.getValue());
@@ -1979,10 +2041,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
checkClientOrGroupActive(loan); checkClientOrGroupActive(loan);
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId); LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(), loanDisbursementDetails); this.loanEventApiJsonValidator.validateUpdateDisbursementDate(command.json(), loanDisbursementDetails);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(), final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue()); CalendarEntityType.LOANS.getValue());
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
.getDisbursementDate().toDate()); .getDisbursementDate().toDate());
@@ -1993,9 +2055,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Map<String, Object> changes = new LinkedHashMap<>(); final Map<String, Object> changes = new LinkedHashMap<>();
CalendarInstance restCalendarInstance = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
}
final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command,
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
this.loanRepository.saveAndFlush(loan); this.loanRepository.saveAndFlush(loan);
@@ -2085,6 +2152,56 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
return transaction; return transaction;
} }
@Override
@CronTarget(jobName = JobName.RECALCULATE_INTEREST_FOR_LOAN)
public void recalculateInterest() {
Collection<Long> loanIds = this.loanReadPlatformService.fetchArrearLoans();
for (Long loanId : loanIds) {
recalculateInterest(loanId);
}
}
@Transactional
public void recalculateInterest(final long loanId) {
Loan loan = this.loanAssembler.assembleFrom(loanId);
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
ChangedTransactionDetail changedTransactionDetail = loan.recalculateSchedule(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance,
existingTransactionIds, existingReversedTransactionIds);
this.loanRepository.save(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
}
private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) { private void updateLoanTransaction(final Long loanTransactionId, final LoanTransaction newLoanTransaction) {
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId); final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
if (transferTransaction != null) { if (transferTransaction != null) {