MIFOSX-1421 : loan recalculation changes
This commit is contained in:
@@ -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';
|
||||
+2
-1
@@ -22,7 +22,8 @@ public enum JobName {
|
||||
UPDATE_NPA("Update Non Performing Assets"),
|
||||
UPDATE_DEPOSITS_ACCOUNT_MATURITY_DETAILS("Update Deposit Accounts Maturity details"),
|
||||
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;
|
||||
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
|
||||
*/
|
||||
CalendarInstance findByEntityIdAndEntityTypeIdAndCalendarTypeId(Long entityId, Integer entityTypeId, Integer calendarTypeId);
|
||||
|
||||
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
||||
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
||||
@Query("from CalendarInstance ci where ci.entityId = :entityId and ci.entityTypeId = :entityTypeId")
|
||||
CalendarInstance findCalendarInstaneByEntityId(@Param("entityId") Long entityId, @Param("entityTypeId") Integer entityTypeId);
|
||||
|
||||
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
|
||||
|
||||
|
||||
+6
-5
@@ -90,12 +90,13 @@ public class LoanTransactionsApiResource {
|
||||
transactionData = this.loanReadPlatformService.retrieveNewClosureDetails();
|
||||
} else if (is(commandParam, "disburse")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, true);
|
||||
} else if(is(commandParam, "disburseToSavings")){
|
||||
} else if (is(commandParam, "disburseToSavings")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveDisbursalTemplate(loanId, false);
|
||||
} else if(is(commandParam, "recoverypayment")){
|
||||
} else if (is(commandParam, "recoverypayment")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveRecoveryPaymentTemplate(loanId);
|
||||
}
|
||||
else {
|
||||
} else if (is(commandParam, "prepayLoan")) {
|
||||
transactionData = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanId);
|
||||
} else {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
|
||||
@@ -150,7 +151,7 @@ public class LoanTransactionsApiResource {
|
||||
} else if (is(commandParam, "undowriteoff")) {
|
||||
final CommandWrapper commandRequest = builder.undoWriteOffLoanTransaction(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}else if(is(commandParam,"recoverypayment")){
|
||||
} else if (is(commandParam, "recoverypayment")) {
|
||||
final CommandWrapper commandRequest = builder.loanRecoveryPaymentTransaction(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+254
-22
@@ -60,6 +60,7 @@ import org.mifosplatform.organisation.staff.domain.Staff;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
|
||||
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.charge.domain.ChargeCalculationType;
|
||||
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)
|
||||
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,
|
||||
final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose,
|
||||
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
|
||||
// reporcessing the repayment schedule.
|
||||
if(this.charges == null) {
|
||||
this.charges = new HashSet<>();
|
||||
if (this.charges == null) {
|
||||
this.charges = new HashSet<>();
|
||||
}
|
||||
this.charges.add(loanCharge);
|
||||
this.summary = updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
@@ -1511,7 +1515,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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()) {
|
||||
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 Map<String, Object> actualChanges, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
|
||||
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,
|
||||
LoanStatus.fromInt(this.loanStatus));
|
||||
@@ -1765,6 +1771,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
|
||||
}
|
||||
}
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
|
||||
&& fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now())) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
|
||||
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
}
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
updateLoanSummaryDerivedFields();
|
||||
handleDisbursementTransaction(actualDisbursementDate);
|
||||
@@ -2127,7 +2138,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
boolean isEmiAmountChanged = this.loanTermVariations.size() > 0;
|
||||
updateLoanToPreDisbursalState();
|
||||
if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged) {
|
||||
if (isScheduleRegenerateRequired || isDisbueseAmtChanged || isEmiAmountChanged
|
||||
|| this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
// clear off actual disbusrement date so schedule regeneration
|
||||
// uses expected date.
|
||||
|
||||
@@ -2186,7 +2198,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
|
||||
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);
|
||||
|
||||
@@ -2197,7 +2211,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
|
||||
loanLifecycleStateMachine, null);
|
||||
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||
holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
@@ -2205,7 +2220,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
|
||||
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;
|
||||
if (isRecoveryRepayment) {
|
||||
@@ -2224,7 +2241,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
|
||||
loanLifecycleStateMachine, null);
|
||||
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||
holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
@@ -2289,7 +2307,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -2353,10 +2374,36 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.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,
|
||||
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();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges());
|
||||
@@ -2384,6 +2431,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
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() {
|
||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
@@ -2556,7 +2614,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
|
||||
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
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);
|
||||
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
|
||||
@@ -2589,13 +2649,17 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
|
||||
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
|
||||
transactionForAdjustment);
|
||||
transactionForAdjustment, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
|
||||
holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
}
|
||||
|
||||
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);
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
@@ -2606,6 +2670,10 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
|
||||
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
}
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
|
||||
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 AppUser currentUser, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
|
||||
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays) {
|
||||
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor);
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
|
||||
calendarInstanceForInterestRecalculation);
|
||||
|
||||
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
|
||||
|
||||
@@ -2739,14 +2808,18 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays,
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
final CalendarInstance calendarInstanceForInterestRecalculation) {
|
||||
ChangedTransactionDetail changedTransactionDetail = null;
|
||||
if (isDisbursementAllowed() && atleastOnceDisbursed()) {
|
||||
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
|
||||
removeDisbursementDetail();
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
|
||||
workingDays);
|
||||
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
|
||||
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(),
|
||||
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 LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
|
||||
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays) {
|
||||
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
|
||||
|
||||
validateAccountStatus(LoanEvent.LOAN_CLOSED);
|
||||
|
||||
@@ -2804,7 +2877,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor);
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
|
||||
calendarInstanceForInterestRecalculation);
|
||||
|
||||
LoanTransaction loanTransaction = null;
|
||||
if (isOpen()) {
|
||||
@@ -3829,7 +3903,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
final Map<String, Object> actualChanges, final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
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);
|
||||
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
|
||||
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate());
|
||||
@@ -3842,6 +3916,11 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
|
||||
workingDays);
|
||||
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
|
||||
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation);
|
||||
}
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategy);
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
|
||||
@@ -3916,4 +3995,157 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
+3
@@ -9,6 +9,7 @@ import java.math.BigDecimal;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
|
||||
public interface LoanAccountDomainService {
|
||||
@@ -27,4 +28,6 @@ public interface LoanAccountDomainService {
|
||||
|
||||
LoanTransaction makeDisburseTransaction(Long loanId, LocalDate transactionDate, BigDecimal transactionAmount,
|
||||
PaymentDetail paymentDetail, String noteText, String txnExternalId);
|
||||
|
||||
LocalDate getCalculatedRepaymentsStartingFromDate(LocalDate actualDisbursementDate, Loan loan, CalendarInstance calendarInstance);
|
||||
}
|
||||
|
||||
+64
-2
@@ -30,11 +30,18 @@ import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.account.domain.AccountTransferRepository;
|
||||
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.exception.ClientNotActiveException;
|
||||
import org.mifosplatform.portfolio.group.domain.Group;
|
||||
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.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.mifosplatform.portfolio.note.domain.Note;
|
||||
import org.mifosplatform.portfolio.note.domain.NoteRepository;
|
||||
import org.mifosplatform.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
@@ -57,6 +64,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final NoteRepository noteRepository;
|
||||
private final AccountTransferRepository accountTransferRepository;
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
private final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository;
|
||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanAccountDomainServiceJpa(final LoanAssembler loanAccountAssembler, final LoanRepository loanRepository,
|
||||
@@ -65,7 +75,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final WorkingDaysRepositoryWrapper workingDaysRepository,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepositoryWrapper,
|
||||
final JournalEntryWritePlatformService journalEntryWritePlatformService,
|
||||
final AccountTransferRepository accountTransferRepository) {
|
||||
final AccountTransferRepository accountTransferRepository, final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
|
||||
final CalendarInstanceRepository calendarInstanceRepository) {
|
||||
this.loanAccountAssembler = loanAccountAssembler;
|
||||
this.loanRepository = loanRepository;
|
||||
this.loanTransactionRepository = loanTransactionRepository;
|
||||
@@ -76,6 +88,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
this.applicationCurrencyRepositoryWrapper = applicationCurrencyRepositoryWrapper;
|
||||
this.journalEntryWritePlatformService = journalEntryWritePlatformService;
|
||||
this.accountTransferRepository = accountTransferRepository;
|
||||
this.loanScheduleFactory = loanScheduleFactory;
|
||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||
this.calendarInstanceRepository = calendarInstanceRepository;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -118,9 +133,28 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
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,
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay, isRecoveryRepayment);
|
||||
holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory,
|
||||
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
|
||||
|
||||
try {
|
||||
this.loanTransactionRepository.save(newRepaymentTransaction);
|
||||
@@ -324,6 +358,34 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
|
||||
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) {
|
||||
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
|
||||
if (transferTransaction != null) {
|
||||
|
||||
+51
-6
@@ -8,10 +8,12 @@ package org.mifosplatform.portfolio.loanaccount.domain;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
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.exception.LoanChargeWithoutMandatoryFieldException;
|
||||
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@@ -695,7 +698,12 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
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 {
|
||||
amountPaidOnThisCharge = processAmount;
|
||||
@@ -800,14 +808,18 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
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) {
|
||||
if (loanChargePerInstallment.isPending()
|
||||
&& trasferedAmount.getAmount().equals(
|
||||
loanChargePerInstallment.getAmountThroughChargePayment(trasferedAmount.getCurrency()).getAmount())) { return loanChargePerInstallment
|
||||
.getRepaymentInstallment(); }
|
||||
&& loanChargePerInstallment.getAmountThroughChargePayment(currency).isGreaterThanZero()) {
|
||||
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
|
||||
loanChargePerInstallment.getAmountThroughChargePayment(currency),
|
||||
loanChargePerInstallment.getRepaymentInstallment(), isFeeCharge());
|
||||
chargePaidDetails.add(chargePaidDetail);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return chargePaidDetails;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
@@ -842,4 +854,37 @@ public class LoanCharge extends AbstractPersistable<Long> {
|
||||
public void updateOverdueInstallmentCharge(LoanOverdueInstallmentCharge 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+31
-3
@@ -153,7 +153,7 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
|
||||
|
||||
Money amountPaidToDate = Money.of(incrementBy.getCurrency(), this.amountPaid);
|
||||
final Money amountOutstanding = Money.of(incrementBy.getCurrency(), this.amountOutstanding);
|
||||
|
||||
Money amountPaidPreviously = amountPaidToDate;
|
||||
Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
|
||||
if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
|
||||
amountPaidOnThisCharge = amountOutstanding;
|
||||
@@ -166,8 +166,20 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
|
||||
this.amountPaid = amountPaidToDate.getAmount();
|
||||
this.amountOutstanding = calculateAmountOutstanding(incrementBy.getCurrency());
|
||||
}
|
||||
this.amountThroughChargePayment = feeAmount.getAmount();
|
||||
this.paid = determineIfFullyPaid();
|
||||
Money amountFromChargePayment = Money.of(incrementBy.getCurrency(), this.amountThroughChargePayment);
|
||||
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;
|
||||
}
|
||||
@@ -195,4 +207,20 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> {
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
+9
@@ -26,6 +26,7 @@ import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
@Table(name = "m_loan_recalculation_details")
|
||||
public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@OneToOne
|
||||
@JoinColumn(name = "loan_id", nullable = false)
|
||||
private Loan loan;
|
||||
@@ -65,4 +66,12 @@ public class LoanInterestRecalculationDetails extends AbstractPersistable<Long>
|
||||
this.rescheduleStrategyMethod = rescheduleStrategyMethod;
|
||||
}
|
||||
|
||||
public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() {
|
||||
return InterestRecalculationCompoundingMethod.fromInt(this.interestRecalculationCompoundingMethod);
|
||||
}
|
||||
|
||||
public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() {
|
||||
return LoanRescheduleStrategyMethod.fromInt(this.rescheduleStrategyMethod);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+8
@@ -532,4 +532,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
||||
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);
|
||||
}
|
||||
}
|
||||
+4
@@ -527,4 +527,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
|
||||
this.externalId = externalId;
|
||||
}
|
||||
|
||||
public boolean isAccrual() {
|
||||
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
}
|
||||
+127
-37
@@ -6,13 +6,16 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
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.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
@@ -69,36 +72,47 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>();
|
||||
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
|
||||
if (loanTransaction.isChargePayment()) {
|
||||
final List<LoanRepaymentScheduleInstallment> chargePaymentInstallments = new ArrayList<>();
|
||||
List<LoanChargePaidDetail> chargePaidDetails = new ArrayList<>();
|
||||
final Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
|
||||
final Set<LoanCharge> transferCharges = new HashSet<>();
|
||||
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
|
||||
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
|
||||
transferCharges.add(loanCharge);
|
||||
if (loanCharge.isInstalmentFee()) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (loanCharge.isFeeCharge()) {
|
||||
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getFeeChargesPortion(currency));
|
||||
} else {
|
||||
installment = loanCharge.fetchRepaymentInstallment(loanTransaction.getPenaltyChargesPortion(currency));
|
||||
}
|
||||
if (installment != null) {
|
||||
chargePaymentInstallments.add(installment);
|
||||
}
|
||||
chargePaidDetails.addAll(loanCharge.fetchRepaymentInstallment(currency));
|
||||
}
|
||||
}
|
||||
LocalDate startDate = disbursementDate;
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
for (final LoanCharge loanCharge : transferCharges) {
|
||||
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(startDate, installment.getDueDate())) {
|
||||
chargePaymentInstallments.add(installment);
|
||||
LoanChargePaidDetail chargePaidDetail = new LoanChargePaidDetail(
|
||||
installment.getFeeChargesOutstanding(currency), installment, loanCharge.isFeeCharge());
|
||||
chargePaidDetails.add(chargePaidDetail);
|
||||
break;
|
||||
}
|
||||
}
|
||||
startDate = installment.getDueDate();
|
||||
}
|
||||
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 {
|
||||
transactionstoBeProcessed.add(loanTransaction);
|
||||
}
|
||||
@@ -152,12 +166,73 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
public void handleTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
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.
|
||||
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;
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
||||
if (amountToProcess != null) {
|
||||
transactionAmountUnprocessed = amountToProcess;
|
||||
}
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
|
||||
@@ -182,30 +257,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
installmentIndex++;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return transactionAmountUnprocessed;
|
||||
}
|
||||
|
||||
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) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
+8
@@ -6,10 +6,12 @@
|
||||
package org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
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.LoanCharge;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
@@ -26,4 +28,10 @@ public interface LoanRepaymentScheduleTransactionProcessor {
|
||||
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
|
||||
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);
|
||||
|
||||
}
|
||||
+8
-2
@@ -39,8 +39,14 @@ public class EarlyPaymentLoanRepaymentScheduleTransactionProcessor extends Abstr
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
+8
-2
@@ -64,8 +64,14 @@ public class InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
+8
-2
@@ -78,8 +78,14 @@ public class MifosStyleLoanRepaymentScheduleTransactionProcessor extends Abstrac
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
+8
-2
@@ -64,8 +64,14 @@ public class PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionPr
|
||||
Money penaltyChargesPortion = Money.zero(transactionAmountRemaining.getCurrency());
|
||||
|
||||
if (loanTransaction.isChargesWaiver()) {
|
||||
// zero this type of transaction and ignore it for now.
|
||||
transactionAmountRemaining = Money.zero(currency);
|
||||
penaltyChargesPortion = currentInstallment.waivePenaltyChargesComponent(transactionDate,
|
||||
loanTransaction.getPenaltyChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(penaltyChargesPortion);
|
||||
|
||||
feeChargesPortion = currentInstallment
|
||||
.waiveFeeChargesComponent(transactionDate, loanTransaction.getFeeChargesPortion(currency));
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(feeChargesPortion);
|
||||
|
||||
} else if (loanTransaction.isInterestWaiver()) {
|
||||
interestPortion = currentInstallment.waiveInterestComponent(transactionDate, transactionAmountRemaining);
|
||||
transactionAmountRemaining = transactionAmountRemaining.minus(interestPortion);
|
||||
|
||||
+360
-9
@@ -9,8 +9,11 @@ import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.joda.time.Days;
|
||||
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.Money;
|
||||
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.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.MultiDisbursementEmiAmountException;
|
||||
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,
|
||||
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
|
||||
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
|
||||
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,
|
||||
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(
|
||||
this.paymentPeriodsInOneYearCalculator, mc);
|
||||
|
||||
@@ -91,10 +107,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
|
||||
outstandingBalance = outstandingBalance.zero().plus(disburseAmt);
|
||||
}
|
||||
Money reducePrincipal = totalCumulativePrincipal.zero();
|
||||
Money fixedEmiAmount = totalCumulativePrincipal.zero();
|
||||
while (!outstandingBalance.isZero()) {
|
||||
isFirstRepayment = false;
|
||||
actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms,
|
||||
isFirstRepayment);
|
||||
isFirstRepayment = false;
|
||||
LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
|
||||
isHolidayEnabled, holidays, workingDays);
|
||||
final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
|
||||
@@ -127,12 +145,40 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
.calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate,
|
||||
loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(),
|
||||
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
|
||||
final PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
|
||||
this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
|
||||
totalCumulativeInterest, totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
|
||||
daysInPeriodApplicableForInterest, outstandingBalance, loanApplicationTerms, periodNumber, mc);
|
||||
daysInPeriodApplicableForInterest, balanceForcalculation, loanApplicationTerms, periodNumber, mc);
|
||||
|
||||
if (loanApplicationTerms.getFixedEmiAmount() != null
|
||||
&& loanApplicationTerms.getFixedEmiAmount().compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) {
|
||||
@@ -141,32 +187,102 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
loanApplicationTerms.getFixedEmiAmount());
|
||||
}
|
||||
// update cumulative fields for principal & interest
|
||||
Money interestForThisinstallment = principalInterestForThisPeriod.interest();
|
||||
totalCumulativePrincipal = totalCumulativePrincipal.plus(principalInterestForThisPeriod.principal());
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(principalInterestForThisPeriod.interest());
|
||||
totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment);
|
||||
totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
|
||||
|
||||
// 6. update outstandingLoanBlance using correct 'principalDue'
|
||||
outstandingBalance = outstandingBalance.minus(principalInterestForThisPeriod.principal());
|
||||
Money actualOutstandingbalance = outstandingBalance;
|
||||
|
||||
Money feeChargesForInstallment = 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
|
||||
final Money totalInstallmentDue = principalInterestForThisPeriod.principal() //
|
||||
.plus(principalInterestForThisPeriod.interest()) //
|
||||
final Money totalInstallmentDue = principalForThisPeriod//
|
||||
.plus(interestForThisinstallment) //
|
||||
.plus(feeChargesForInstallment) //
|
||||
.plus(penaltyChargesForInstallment);
|
||||
|
||||
// 9. create repayment period from parts
|
||||
final LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(periodNumber, periodStartDate,
|
||||
scheduledDueDate, principalInterestForThisPeriod.principal(), outstandingBalance,
|
||||
principalInterestForThisPeriod.interest(), feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue);
|
||||
scheduledDueDate, principalForThisPeriod, actualOutstandingbalance, interestForThisinstallment,
|
||||
feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue);
|
||||
periods.add(installment);
|
||||
|
||||
// handle cumulative fields
|
||||
loanTermInDays += daysInPeriod;
|
||||
totalPrincipalExpected = totalPrincipalExpected.add(principalInterestForThisPeriod.principal().getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
totalInterestCharged = totalInterestCharged.add(interestForThisinstallment.getAmount());
|
||||
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
|
||||
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.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,
|
||||
totalPrincipalPaid, totalInterestCharged, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected,
|
||||
totalOutstanding);
|
||||
@@ -366,4 +516,205 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
+112
-8
@@ -16,15 +16,19 @@ import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
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.DaysInYearType;
|
||||
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
|
||||
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.InterestCalculationPeriodMethod;
|
||||
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.LoanRescheduleStrategyMethod;
|
||||
|
||||
public final class LoanApplicationTerms {
|
||||
|
||||
@@ -107,8 +111,14 @@ public final class LoanApplicationTerms {
|
||||
private final DaysInMonthType daysInMonthType;
|
||||
|
||||
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,
|
||||
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 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,
|
||||
repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType,
|
||||
annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate,
|
||||
repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
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,
|
||||
@@ -160,6 +174,9 @@ public final class LoanApplicationTerms {
|
||||
final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType();
|
||||
final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType();
|
||||
final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled();
|
||||
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
|
||||
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
|
||||
final CalendarInstance restCalendarInstance = null;
|
||||
|
||||
return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments,
|
||||
repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod,
|
||||
@@ -167,7 +184,54 @@ public final class LoanApplicationTerms {
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment,
|
||||
graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount,
|
||||
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,
|
||||
@@ -182,7 +246,8 @@ public final class LoanApplicationTerms {
|
||||
final boolean multiDisburseLoan, final BigDecimal emiAmount, final List<DisbursementData> disbursementDatas,
|
||||
final BigDecimal maxOutstandingBalance, final List<LoanTermVariationsData> emiAmountVariations,
|
||||
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.loanTermFrequency = loanTermFrequency;
|
||||
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
|
||||
@@ -216,7 +281,10 @@ public final class LoanApplicationTerms {
|
||||
this.graceOnArrearsAgeing = graceOnArrearsAgeing;
|
||||
this.daysInMonthType = daysInMonthType;
|
||||
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,
|
||||
@@ -593,6 +661,18 @@ public final class LoanApplicationTerms {
|
||||
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) {
|
||||
|
||||
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) {
|
||||
|
||||
if (multiDisburseLoan && fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); }
|
||||
if (fixedEmiAmount != null) { return fixedEmiAmount.doubleValue(); }
|
||||
final double futureValue = 0;
|
||||
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.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod,
|
||||
this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(),
|
||||
this.daysInYearType.getValue(), this.isInterestRecalculationEnabled);
|
||||
this.daysInYearType.getValue(), this.interestRecalculationEnabled);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+13
@@ -11,11 +11,24 @@ import java.util.Set;
|
||||
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
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.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 {
|
||||
|
||||
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
|
||||
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);
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+44
@@ -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;
|
||||
}
|
||||
}
|
||||
+4
@@ -87,4 +87,8 @@ public interface LoanReadPlatformService {
|
||||
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
|
||||
|
||||
Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
|
||||
|
||||
Collection<Long> fetchArrearLoans();
|
||||
|
||||
LoanTransactionData retrieveLoanPrePaymentTemplate(Long loanId);
|
||||
}
|
||||
+46
-1
@@ -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.LoanSchedulePeriodData;
|
||||
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.TransactionProcessingStrategyData;
|
||||
import org.mifosplatform.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
|
||||
@@ -120,6 +121,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
private final LoanMapper loaanLoanMapper = new LoanMapper();
|
||||
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||
private final DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd");
|
||||
private final LoanScheduleGeneratorFactory loanScheduleFactory;
|
||||
|
||||
@Autowired
|
||||
public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository,
|
||||
@@ -129,7 +131,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
final GroupReadPlatformService groupReadPlatformService, final LoanDropdownReadPlatformService loanDropdownReadPlatformService,
|
||||
final FundReadPlatformService fundReadPlatformService, final ChargeReadPlatformService chargeReadPlatformService,
|
||||
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.loanRepository = loanRepository;
|
||||
this.loanTransactionRepository = loanTransactionRepository;
|
||||
@@ -145,6 +148,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
this.staffReadPlatformService = staffReadPlatformService;
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
|
||||
this.loanScheduleFactory = loanScheduleFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -364,6 +368,29 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -79,4 +79,6 @@ public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult updateDisbursementDateForTranche(Long loanId, Long disbursementId, JsonCommand command);
|
||||
|
||||
void recalculateInterest();
|
||||
|
||||
}
|
||||
+173
-56
@@ -61,7 +61,6 @@ import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||
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.ChargePaymentMode;
|
||||
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.LoanUpdateCommandFromApiJsonDeserializer;
|
||||
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.LinkedAccountRequiredException;
|
||||
import org.mifosplatform.portfolio.loanproduct.service.LoanProductReadPlatformService;
|
||||
@@ -237,7 +235,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
checkForProductMixRestrictions(loan);
|
||||
|
||||
// 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());
|
||||
if (loan.isSyncDisbursementWithMeeting()) {
|
||||
|
||||
@@ -257,8 +255,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
// Recalculate first repayment date based in actual disbursement date.
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan,
|
||||
calendarInstance);
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
|
||||
actualDisbursementDate, loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
|
||||
actualDisbursementDate.toDate());
|
||||
@@ -282,9 +280,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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,
|
||||
existingReversedTransactionIds, changes, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule);
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
|
||||
@@ -333,7 +337,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
|
||||
|
||||
updateRecurringCalendarDatesForInterestRecalculation(loan);
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
@@ -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
|
||||
*
|
||||
@@ -462,9 +439,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
disbursementTransaction.updateLoan(loan);
|
||||
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,
|
||||
existingReversedTransactionIds, changes, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays,
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule);
|
||||
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay, recalcualteSchedule, restCalendarInstance);
|
||||
}
|
||||
if (!changes.isEmpty()) {
|
||||
try {
|
||||
@@ -541,11 +524,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
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());
|
||||
final LocalDate actualDisbursementDate = loan.getDisbursementDate();
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(actualDisbursementDate, loan,
|
||||
calendarInstance);
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
|
||||
actualDisbursementDate, loan, calendarInstance);
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
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 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,
|
||||
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 (paymentDetail != null) {
|
||||
@@ -783,8 +785,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LoanTransaction waiveInterestTransaction = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney,
|
||||
transactionDate);
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
|
||||
defaultLoanLifecycleStateMachine(), 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();
|
||||
}
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan
|
||||
.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
|
||||
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory,
|
||||
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
|
||||
|
||||
this.loanTransactionRepository.save(waiveInterestTransaction);
|
||||
|
||||
@@ -844,10 +870,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final List<Long> existingTransactionIds = 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());
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
|
||||
calendarInstance);
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
|
||||
loan.getDisbursementDate(), loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
|
||||
.getDisbursementDate().toDate());
|
||||
@@ -857,9 +883,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
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(),
|
||||
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory,
|
||||
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
|
||||
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
|
||||
LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
this.loanTransactionRepository.save(writeoff);
|
||||
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> existingReversedTransactionIds = new ArrayList<>();
|
||||
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByLoanId(loan.getId(),
|
||||
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
|
||||
calendarInstance);
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
|
||||
loan.getDisbursementDate(), loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
|
||||
.getDisbursementDate().toDate());
|
||||
@@ -919,9 +951,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
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,
|
||||
existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency,
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
|
||||
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
|
||||
if (possibleClosingTransaction != null) {
|
||||
this.loanTransactionRepository.save(possibleClosingTransaction);
|
||||
@@ -1429,9 +1467,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final LoanTransaction newTransferAcceptanceTransaction = LoanTransaction.approveTransfer(acceptedInOffice, loan, transferDate);
|
||||
loan.getLoanTransactions().add(newTransferAcceptanceTransaction);
|
||||
if(loan.getTotalOverpaid() !=null){
|
||||
if (loan.getTotalOverpaid() != null) {
|
||||
loan.setLoanStatus(LoanStatus.OVERPAID.getValue());
|
||||
}else{
|
||||
} else {
|
||||
loan.setLoanStatus(LoanStatus.ACTIVE.getValue());
|
||||
}
|
||||
if (loanOfficer != null) {
|
||||
@@ -1953,7 +1991,31 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
if (!loan.isClosedWrittenOff()) { throw new PlatformServiceUnavailableException(
|
||||
"error.msg.loan.status.not.written.off.update.not.allowed", "Loan :" + 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) {
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
this.loanTransactionRepository.save(mapEntry.getValue());
|
||||
@@ -1979,10 +2041,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
checkClientOrGroupActive(loan);
|
||||
LoanDisbursementDetails loanDisbursementDetails = loan.fetchLoanDisbursementsById(disbursementId);
|
||||
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());
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan,
|
||||
calendarInstance);
|
||||
final LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
|
||||
loan.getDisbursementDate(), loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan
|
||||
.getDisbursementDate().toDate());
|
||||
@@ -1993,9 +2055,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final List<Long> existingTransactionIds = new ArrayList<>();
|
||||
final List<Long> existingReversedTransactionIds = new ArrayList<>();
|
||||
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,
|
||||
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency,
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
|
||||
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
|
||||
|
||||
this.loanRepository.saveAndFlush(loan);
|
||||
|
||||
@@ -2085,6 +2152,56 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
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) {
|
||||
final AccountTransferTransaction transferTransaction = this.accountTransferRepository.findByToLoanTransactionId(loanTransactionId);
|
||||
if (transferTransaction != null) {
|
||||
|
||||
Reference in New Issue
Block a user