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