MIFOSX-1586 : corrected overdue charges with interest calculation

This commit is contained in:
Pramod Nuthakki
2014-10-26 13:47:48 +05:30
parent 9863f8d6a3
commit 659ffa3e9a
31 changed files with 1520 additions and 649 deletions
@@ -2444,11 +2444,15 @@ public class ClientLoanIntegrationTest {
loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0"); validateCharge(flatInstallmentFee, loanCharges, "50", "100.00", "50.0", "50.0");
/*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("50.0"), /*
JournalEntry.TransactionType.CREDIT)); * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", * "20 September 2011", new JournalEntry(Float.valueOf("50.0"),
new JournalEntry(Float.valueOf("50.0"), JournalEntry.TransactionType.DEBIT)); * JournalEntry.TransactionType.CREDIT));
*/ * this.journalEntryHelper.checkJournalEntryForExpenseAccount
* (expenseAccount, "20 September 2011", new
* JournalEntry(Float.valueOf("50.0"),
* JournalEntry.TransactionType.DEBIT));
*/
final String jobName = "Add Accrual Transactions"; final String jobName = "Add Accrual Transactions";
try { try {
this.schedulerJobHelper.excuteJob(jobName); this.schedulerJobHelper.excuteJob(jobName);
@@ -2645,10 +2649,15 @@ public class ClientLoanIntegrationTest {
loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70"); validateCharge(percentageInstallmentFee, loanCharges, "1", "61.19", "29.11", "29.70");
/*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry(Float.valueOf("29.7"), /*
JournalEntry.TransactionType.CREDIT)); * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", * "20 September 2011", new JournalEntry(Float.valueOf("29.7"),
new JournalEntry(Float.valueOf("29.7"), JournalEntry.TransactionType.DEBIT));*/ * JournalEntry.TransactionType.CREDIT));
* this.journalEntryHelper.checkJournalEntryForExpenseAccount
* (expenseAccount, "20 September 2011", new
* JournalEntry(Float.valueOf("29.7"),
* JournalEntry.TransactionType.DEBIT));
*/
final String jobName = "Add Accrual Transactions"; final String jobName = "Add Accrual Transactions";
try { try {
@@ -2850,10 +2859,15 @@ public class ClientLoanIntegrationTest {
loanCharges = this.loanTransactionHelper.getLoanCharges(loanID); loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51"); validateCharge(amountPlusInterestPercentageInstallmentFee, loanCharges, "1", "63.02", "31.51", "31.51");
/*this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount, "20 September 2011", new JournalEntry( /*
Float.valueOf("31.51"), JournalEntry.TransactionType.CREDIT)); * this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
this.journalEntryHelper.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011", * "20 September 2011", new JournalEntry( Float.valueOf("31.51"),
new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.DEBIT));*/ * JournalEntry.TransactionType.CREDIT));
* this.journalEntryHelper.checkJournalEntryForExpenseAccount
* (expenseAccount, "20 September 2011", new
* JournalEntry(Float.valueOf("31.51"),
* JournalEntry.TransactionType.DEBIT));
*/
final String jobName = "Add Accrual Transactions"; final String jobName = "Add Accrual Transactions";
try { try {
@@ -3141,7 +3155,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_EMI_AMOUN,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null,null); LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_SAME_AS_REPAYMENT_PERIOD, "0", null, null);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null, final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, null,
LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, new ArrayList<HashMap>(0)); LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, new ArrayList<HashMap>(0));
@@ -3231,7 +3245,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE,null); LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", LOAN_DISBURSEMENT_DATE, null);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0)); LOAN_DISBURSEMENT_DATE, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0));
@@ -3356,7 +3370,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY, final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE, LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS, LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE,null); LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_WEEKLY, "1", REST_START_DATE, null);
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE, final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges); REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, charges);
@@ -3433,6 +3447,113 @@ public class ClientLoanIntegrationTest {
} }
@Test
public void testLoanScheduleWithInterestRecalculation_WITH_REST_DAILY_INTEREST_COMPOUND_INTEREST_FEE_STRATEGY_WITH_OVERDUE_CHARGE()
throws InterruptedException {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec, this.responseSpec);
DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar todaysDate = Calendar.getInstance();
todaysDate.add(Calendar.DAY_OF_MONTH, -7 * 3);
final String LOAN_DISBURSEMENT_DATE = dateFormat.format(todaysDate.getTime());
todaysDate.add(Calendar.DAY_OF_MONTH, -2);
final String REST_START_DATE = dateFormat.format(todaysDate.getTime());
todaysDate = Calendar.getInstance();
Integer overdueFeeChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec,
ChargesHelper.getLoanOverdueFeeJSONWithCalculattionTypePercentage());
Assert.assertNotNull(overdueFeeChargeId);
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
LoanProductTestBuilder.RECALCULATION_STRATEGY_RESCHEDULE_NEXT_REPAYMENTS,
LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "1", REST_START_DATE, null, overdueFeeChargeId.toString());
final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientID, loanProductID, LOAN_DISBURSEMENT_DATE,
REST_START_DATE, LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, null);
Assert.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
List<Map<String, Object>> expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2505.73", "23.18", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2517.29", "11.62", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.approveLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
System.out.println("-------------------------------DISBURSE LOAN-------------------------------------------");
loanStatusHashMap = this.loanTransactionHelper.disburseLoan(LOAN_DISBURSEMENT_DATE, loanID);
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2482.55", "46.36", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2482.33", "46.58", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2552.36", "11.78", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
String JobName = "Apply penalty to overdue loans";
this.schedulerJobHelper.excuteJob(JobName);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2481.38", "47.53", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2479.99", "48.92", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2555.87", "11.8", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
Calendar repaymentDate = Calendar.getInstance();
repaymentDate.add(Calendar.DAY_OF_MONTH, -7 * 2);
final String LOAN_FIRST_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
Float totalDueForCurrentPeriod = (Float) loanSchedule.get(1).get("totalDueForPeriod");
totalDueForCurrentPeriod = totalDueForCurrentPeriod - new Float("252.89");
this.loanTransactionHelper.makeRepayment(LOAN_FIRST_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2492.89", "36.02", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2530.13", "11.68", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
repaymentDate = Calendar.getInstance();
repaymentDate.add(Calendar.DAY_OF_MONTH, -3);
final String LOAN_SECOND_REPAYMENT_DATE = dateFormat.format(repaymentDate.getTime());
totalDueForCurrentPeriod = (Float) loanSchedule.get(2).get("totalDueForPeriod");
this.loanTransactionHelper.makeRepayment(LOAN_SECOND_REPAYMENT_DATE, totalDueForCurrentPeriod, loanID);
loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec, this.responseSpec, loanID);
expectedvalues = new ArrayList<>();
todaysDate = Calendar.getInstance();
addRepaymentValues(expectedvalues, todaysDate, -2, "2482.76", "46.15", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2494.22", "34.69", "0.0", "252.89");
addRepaymentValues(expectedvalues, todaysDate, 1, "2498.39", "30.52", "0.0", "0.0");
addRepaymentValues(expectedvalues, todaysDate, 1, "2524.63", "11.65", "0.0", "0.0");
verifyLoanRepaymentSchedule(loanSchedule, expectedvalues);
}
@Test @Test
public void testLoanScheduleWithInterestRecalculation_WITH_PERIODIC_ACCOUNTING() { public void testLoanScheduleWithInterestRecalculation_WITH_PERIODIC_ACCOUNTING() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
@@ -3533,7 +3654,7 @@ public class ClientLoanIntegrationTest {
} }
private void addRepaymentValues(List<Map<String, Object>> expectedvalues, Calendar todaysDate, int addMonths, String principalDue, private void addRepaymentValues(List<Map<String, Object>> expectedvalues, Calendar todaysDate, int addMonths, String principalDue,
String interestDue, String feeChargesDue, String penaltyChargesDue) { String interestDue, String feeChargesDue, String penaltyChargesDue) {
Map<String, Object> values = new HashMap<>(3); Map<String, Object> values = new HashMap<>(3);
values.put("dueDate", getDateAsArray(todaysDate, addMonths)); values.put("dueDate", getDateAsArray(todaysDate, addMonths));
@@ -3560,6 +3681,15 @@ public class ClientLoanIntegrationTest {
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
final String recalculationRestFrequencyDate, final Account[] accounts) { final String recalculationRestFrequencyDate, final Account[] accounts) {
return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod,
rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval,
recalculationRestFrequencyDate, accounts, null);
}
private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy,
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
final String recalculationRestFrequencyDate, final Account[] accounts, final String chargeId) {
System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
LoanProductTestBuilder builder = new LoanProductTestBuilder() LoanProductTestBuilder builder = new LoanProductTestBuilder()
.withPrincipal("10000000.00") .withPrincipal("10000000.00")
@@ -3578,7 +3708,7 @@ public class ClientLoanIntegrationTest {
builder = builder.withAccountingRulePeriodicAccrual(accounts); builder = builder.withAccountingRulePeriodicAccrual(accounts);
} }
final String loanProductJSON = builder.build(null); final String loanProductJSON = builder.build(chargeId);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON); return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
} }
@@ -222,6 +222,18 @@ public class ChargesHelper {
System.out.println(chargesCreateJson); System.out.println(chargesCreateJson);
return chargesCreateJson; return chargesCreateJson;
} }
public static String getLoanOverdueFeeJSONWithCalculattionTypePercentage() {
final HashMap<String, Object> map = populateDefaultsForLoan();
map.put("penalty", ChargesHelper.penalty);
map.put("amount", "10");
map.put("chargePaymentMode", ChargesHelper.CHARGE_PAYMENT_MODE_REGULAR);
map.put("chargeTimeType", CHARGE_OVERDUE_INSTALLMENT_FEE);
map.put("chargeCalculationType", ChargesHelper.CHARGE_CALCULATION_TYPE_PERCENTAGE_AMOUNT_AND_INTEREST);
String chargesCreateJson = new Gson().toJson(map);
System.out.println(chargesCreateJson);
return chargesCreateJson;
}
public static HashMap<String, Object> populateDefaultsForLoan() { public static HashMap<String, Object> populateDefaultsForLoan() {
final HashMap<String, Object> map = new HashMap<>(); final HashMap<String, Object> map = new HashMap<>();
@@ -74,7 +74,7 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
// accounting related data validation // accounting related data validation
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4);
if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) { if (isCashBasedAccounting(accountingRuleType) || isAccrualBasedAccounting(accountingRuleType)) {
@@ -0,0 +1,52 @@
package org.mifosplatform.portfolio.loanaccount.data;
import java.util.List;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
public class HolidayDetailDTO {
final boolean isHolidayEnabled;
final List<Holiday> holidays;
final WorkingDays workingDays;
final boolean allowTransactionsOnHoliday;
final boolean allowTransactionsOnNonWorkingDay;
public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
this.isHolidayEnabled = isHolidayEnabled;
this.holidays = holidays;
this.workingDays = workingDays;
this.allowTransactionsOnHoliday = false;
this.allowTransactionsOnNonWorkingDay = false;
}
public HolidayDetailDTO(final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay) {
this.isHolidayEnabled = isHolidayEnabled;
this.holidays = holidays;
this.workingDays = workingDays;
this.allowTransactionsOnHoliday = allowTransactionsOnHoliday;
this.allowTransactionsOnNonWorkingDay = allowTransactionsOnNonWorkingDay;
}
public boolean isHolidayEnabled() {
return this.isHolidayEnabled;
}
public List<Holiday> getHolidays() {
return this.holidays;
}
public WorkingDays getWorkingDays() {
return this.workingDays;
}
public boolean isAllowTransactionsOnHoliday() {
return this.allowTransactionsOnHoliday;
}
public boolean isAllowTransactionsOnNonWorkingDay() {
return this.allowTransactionsOnNonWorkingDay;
}
}
@@ -0,0 +1,97 @@
package org.mifosplatform.portfolio.loanaccount.data;
import org.joda.time.LocalDate;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
public class ScheduleGeneratorDTO {
final LoanScheduleGeneratorFactory loanScheduleFactory;
final ApplicationCurrency applicationCurrency;
final LocalDate calculatedRepaymentsStartingFromDate;
final HolidayDetailDTO holidayDetailDTO;
final CalendarInstance calendarInstanceForInterestRecalculation;
LocalDate recalculateFrom;
final Long overdurPenaltyWaitPeriod;
LocalDate lastTransactionDate;
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO) {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency;
this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
this.calendarInstanceForInterestRecalculation = null;
this.recalculateFrom = null;
this.overdurPenaltyWaitPeriod = null;
this.lastTransactionDate = null;
this.holidayDetailDTO = holidayDetailDTO;
}
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance calendarInstanceForInterestRecalculation, final LocalDate recalculateFrom,
final Long overdurPenaltyWaitPeriod, final LocalDate lastTransactionDate) {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency;
this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
this.calendarInstanceForInterestRecalculation = calendarInstanceForInterestRecalculation;
this.recalculateFrom = recalculateFrom;
this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod;
this.lastTransactionDate = lastTransactionDate;
this.holidayDetailDTO = holidayDetailDTO;
}
public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
return this.loanScheduleFactory;
}
public ApplicationCurrency getApplicationCurrency() {
return this.applicationCurrency;
}
public LocalDate getCalculatedRepaymentsStartingFromDate() {
return this.calculatedRepaymentsStartingFromDate;
}
public CalendarInstance getCalendarInstanceForInterestRecalculation() {
return this.calendarInstanceForInterestRecalculation;
}
public LocalDate getRecalculateFrom() {
return this.recalculateFrom;
}
public Long getOverdurPenaltyWaitPeriod() {
return this.overdurPenaltyWaitPeriod;
}
public int getPenaltyWaitPeriod() {
int penaltyWaitPeriod = 0;
if (this.overdurPenaltyWaitPeriod != null) {
penaltyWaitPeriod = this.overdurPenaltyWaitPeriod.intValue();
}
return penaltyWaitPeriod;
}
public LocalDate getLastTransactionDate() {
return this.lastTransactionDate;
}
public HolidayDetailDTO getHolidayDetailDTO() {
return this.holidayDetailDTO;
}
public void setRecalculateFrom(LocalDate recalculateFrom) {
this.recalculateFrom = recalculateFrom;
}
public void setLastTransactionDate(LocalDate lastTransactionDate) {
this.lastTransactionDate = lastTransactionDate;
}
}
@@ -75,7 +75,9 @@ import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException; import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
@@ -466,10 +468,7 @@ public class Loan extends AbstractPersistable<Long> {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration; this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
} }
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds, public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge) {
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) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
@@ -526,22 +525,14 @@ public class Loan extends AbstractPersistable<Long> {
// store Id's of existing loan transactions and existing reversed loan // store Id's of existing loan transactions and existing reversed loan
// transactions // transactions
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() getLastUserTransactionForChargeCalc());
&& (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else {
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
}
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
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(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
} }
@@ -652,12 +643,18 @@ public class Loan extends AbstractPersistable<Long> {
&& !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) { && !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) {
final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range."; final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage, throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
getDisbursementDate(), getLastRepaymentPeriodDueDate(), loanCharge.name()); getDisbursementDate(), lastRepaymentPeriodDueDate, loanCharge.name());
} }
} }
private LocalDate getLastRepaymentPeriodDueDate() { private LocalDate getLastRepaymentPeriodDueDate() {
return this.repaymentScheduleInstallments.get(this.repaymentScheduleInstallments.size() - 1).getDueDate(); LocalDate lastRepaymentDate = getDisbursementDate();
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
if (!installment.isRecalculatedInterestComponent() && installment.getDueDate().isAfter(lastRepaymentDate)) {
lastRepaymentDate = installment.getDueDate();
}
}
return lastRepaymentDate;
} }
public void removeLoanCharge(final LoanCharge loanCharge) { public void removeLoanCharge(final LoanCharge loanCharge) {
@@ -674,7 +671,8 @@ public class Loan extends AbstractPersistable<Long> {
if (removed) { if (removed) {
charge.setActive(false); charge.setActive(false);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
@@ -693,7 +691,7 @@ public class Loan extends AbstractPersistable<Long> {
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges()); getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
} }
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -751,11 +749,12 @@ public class Loan extends AbstractPersistable<Long> {
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges()); getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
} }
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -838,10 +837,7 @@ public class Loan extends AbstractPersistable<Long> {
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine, public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
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 Integer loanInstallmentNumber, final List<Holiday> holidays, final WorkingDays workingDays, final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) {
final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation,
final Money accruedCharge) {
validateLoanIsNotClosed(loanCharge); validateLoanIsNotClosed(loanCharge);
@@ -887,9 +883,7 @@ public class Loan extends AbstractPersistable<Long> {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) { && (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} }
// Waive of charges whose due date falls after latest 'repayment' // Waive of charges whose due date falls after latest 'repayment'
// transaction dont require entire loan schedule to be reprocessed. // transaction dont require entire loan schedule to be reprocessed.
@@ -906,11 +900,12 @@ public class Loan extends AbstractPersistable<Long> {
***/ ***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement, loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges()); getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
} else { } else {
// reprocess loan schedule based on charge been waived. // reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
} }
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
@@ -1032,34 +1027,74 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanScheduleDependentDerivedFields(); updateLoanScheduleDependentDerivedFields();
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
resetAccruals(); applyAccurals();
} }
/** /**
* method to reverse all the accrual entries * method updates accrual derived fields on installments and reverse the
* unprocessed transactions
*/ */
private void resetAccruals() { private void applyAccurals() {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; } Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions();
List<LoanTransaction> transactions = this.getLoanTransactions(); if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
for (final LoanTransaction transaction : transactions) { applyPeriodicAccruals(accruals);
if (transaction.isAccrual()) { } else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
transaction.reverse(); updateAccrualsForNonPeriodicAccruals(accruals);
}
} }
} }
public boolean isAccrualsPresent() { private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
boolean isAccrualsPresent = false;
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return isAccrualsPresent; } for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
List<LoanTransaction> transactions = this.getLoanTransactions(); Money interest = Money.zero(getCurrency());
for (final LoanTransaction transaction : transactions) { Money fee = Money.zero(getCurrency());
if (transaction.isAccrual() && transaction.isNotReversed()) { Money penality = Money.zero(getCurrency());
isAccrualsPresent = true; for (LoanTransaction loanTransaction : accruals) {
break; if (loanTransaction.getTransactionDate().isAfter(installment.getFromDate())
&& !loanTransaction.getTransactionDate().isAfter(installment.getDueDate())) {
interest = interest.plus(loanTransaction.getInterestPortion(getCurrency()));
fee = fee.plus(loanTransaction.getFeeChargesPortion(getCurrency()));
penality = penality.plus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
if (installment.getFeeChargesCharged(getCurrency()).isLessThan(fee)
|| installment.getInterestCharged(getCurrency()).isLessThan(interest)
|| installment.getPenaltyChargesCharged(getCurrency()).isLessThan(penality)) {
interest = interest.minus(loanTransaction.getInterestPortion(getCurrency()));
fee = fee.minus(loanTransaction.getFeeChargesPortion(getCurrency()));
penality = penality.minus(loanTransaction.getPenaltyChargesPortion(getCurrency()));
loanTransaction.reverse();
}
}
}
installment.updateAccrualPortion(interest, fee, penality);
}
}
private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals) {
final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged());
for (LoanTransaction loanTransaction : accruals) {
if (loanTransaction.getInterestPortion(getCurrency()).isGreaterThanZero()) {
if (loanTransaction.getInterestPortion(getCurrency()).isNotEqualTo(interestApplied)) {
loanTransaction.reverse();
final LoanTransaction interestAppliedTransaction = LoanTransaction.accrueInterest(getOffice(), this, interestApplied,
getDisbursementDate());
this.loanTransactions.add(interestAppliedTransaction);
}
} else {
Set<LoanChargePaidBy> chargePaidBies = loanTransaction.getLoanChargesPaid();
for (final LoanChargePaidBy chargePaidBy : chargePaidBies) {
LoanCharge loanCharge = chargePaidBy.getLoanCharge();
Money chargeAmount = loanCharge.getAmount(getCurrency());
if (chargeAmount.isNotEqualTo(loanTransaction.getAmount(getCurrency()))) {
loanTransaction.reverse();
handleChargeAppliedTransaction(loanCharge, loanTransaction.getTransactionDate());
}
}
} }
} }
return isAccrualsPresent;
} }
private void updateLoanScheduleDependentDerivedFields() { private void updateLoanScheduleDependentDerivedFields() {
@@ -1339,8 +1374,9 @@ public class Loan extends AbstractPersistable<Long> {
public void recalculateAllCharges() { public void recalculateAllCharges() {
Set<LoanCharge> charges = this.charges(); Set<LoanCharge> charges = this.charges();
int penaltyWaitPeriod = 0;
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge); recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
} }
} }
@@ -1375,8 +1411,25 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
private void recalculateLoanCharge(final LoanCharge loanCharge) { private void updateOverdueScheduleInstallment(final LoanCharge loanCharge) {
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isActive()) {
LoanOverdueInstallmentCharge overdueInstallmentCharge = loanCharge.getOverdueInstallmentCharge();
if (overdueInstallmentCharge != null) {
Integer installmentNumber = overdueInstallmentCharge.getInstallment().getInstallmentNumber();
LoanRepaymentScheduleInstallment installment = fetchRepaymentScheduleInstallment(installmentNumber);
overdueInstallmentCharge.updateLoanRepaymentScheduleInstallment(installment);
}
}
}
private void recalculateLoanCharge(final LoanCharge loanCharge, final int penaltyWaitPeriod) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.isOverdueInstallmentCharge()) {
amount = calculateOverdueAmountPercentageAppliedTo(loanCharge, penaltyWaitPeriod);
} else {
amount = calculateAmountPercentageAppliedTo(loanCharge);
}
BigDecimal chargeAmt = BigDecimal.ZERO; BigDecimal chargeAmt = BigDecimal.ZERO;
BigDecimal totalChargeAmt = BigDecimal.ZERO; BigDecimal totalChargeAmt = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageBased()) { if (loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -1390,9 +1443,46 @@ public class Loan extends AbstractPersistable<Long> {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments())); chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
} }
} }
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(), if (loanCharge.isActive()) {
totalChargeAmt); loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate()); totalChargeAmt);
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
}
}
private BigDecimal calculateOverdueAmountPercentageAppliedTo(final LoanCharge loanCharge, final int penaltyWaitPeriod) {
LoanRepaymentScheduleInstallment installment = loanCharge.getOverdueInstallmentCharge().getInstallment();
LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod);
Money amount = Money.zero(getCurrency());
if (graceDate.isAfter(installment.getDueDate())) {
amount = calculateOverdueAmountPercentageAppliedTo(installment, loanCharge.getChargeCalculation());
if (!amount.isGreaterThanZero()) {
loanCharge.setActive(false);
}
} else {
loanCharge.setActive(false);
}
return amount.getAmount();
}
private Money calculateOverdueAmountPercentageAppliedTo(LoanRepaymentScheduleInstallment installment,
ChargeCalculationType calculationType) {
Money amount = Money.zero(getCurrency());
switch (calculationType) {
case PERCENT_OF_AMOUNT:
amount = installment.getPrincipalOutstanding(getCurrency());
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
amount = installment.getPrincipalOutstanding(getCurrency()).plus(installment.getInterestOutstanding(getCurrency()));
break;
case PERCENT_OF_INTEREST:
amount = installment.getInterestOutstanding(getCurrency());
break;
default:
break;
}
return amount;
} }
private void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) { private void updateDisbursementDetails(final JsonCommand command, final Map<String, Object> actualChanges) {
@@ -1588,8 +1678,9 @@ public class Loan extends AbstractPersistable<Long> {
} }
// charges are optional // charges are optional
int penaltyWaitPeriod = 0;
for (final LoanCharge loanCharge : charges()) { for (final LoanCharge loanCharge : charges()) {
recalculateLoanCharge(loanCharge); recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
} }
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1815,11 +1906,8 @@ public class Loan extends AbstractPersistable<Long> {
return ids; return ids;
} }
public ChangedTransactionDetail disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
final JsonCommand command, final ApplicationCurrency currency, final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay,
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));
@@ -1836,15 +1924,15 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("dateFormat", command.dateFormat()); actualChanges.put("dateFormat", command.dateFormat());
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate")); actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
// validate if disbursement date is a holiday or a non-working day // validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnNonWorkingDay(holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled() if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) { && (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} }
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1870,9 +1958,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
public void regenerateScheduleOnDisbursement(final LoanScheduleGeneratorFactory loanScheduleFactory, public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean recalculateSchedule,
final LocalDate actualDisbursementDate, BigDecimal emiAmount) { final LocalDate actualDisbursementDate, BigDecimal emiAmount) {
boolean isEmiAmountChanged = false; boolean isEmiAmountChanged = false;
if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) { if (this.loanProduct.isMultiDisburseLoan() && emiAmount != null && emiAmount.compareTo(retriveLastEmiAmount()) != 0) {
@@ -1883,8 +1969,7 @@ public class Loan extends AbstractPersistable<Long> {
} }
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) { if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, regenerateRepaymentSchedule(scheduleGeneratorDTO);
workingDays);
} }
} }
@@ -1958,7 +2043,8 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
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(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
} }
@@ -2068,25 +2154,20 @@ public class Loan extends AbstractPersistable<Long> {
* Ability to regenerate the repayment schedule based on the loans current * Ability to regenerate the repayment schedule based on the loans current
* details/state. * details/state.
*/ */
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, private void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final LoanScheduleModel loanSchedule = regenerateScheduleModel(loanScheduleFactory, applicationCurrency, final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO);
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
updateLoanSchedule(loanSchedule); updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges(); Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) { for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge); recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod());
} }
} }
public LoanScheduleModel regenerateScheduleModel(final LoanScheduleGeneratorFactory loanScheduleFactory, public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); final LoanScheduleGenerator loanScheduleGenerator = scheduleGeneratorDTO.getLoanScheduleFactory().create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
@@ -2113,14 +2194,14 @@ public class Loan extends AbstractPersistable<Long> {
loanVariationTermsData.add(data); loanVariationTermsData.add(data);
} }
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(),
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(), loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail, scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance, this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate()); loanVariationTermsData, getInterestChargedFromDate());
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(), final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
isHolidayEnabled, holidays, workingDays); scheduleGeneratorDTO.getHolidayDetailDTO());
return loanSchedule; return loanSchedule;
} }
@@ -2220,10 +2301,8 @@ public class Loan extends AbstractPersistable<Long> {
return chargesPayment; return chargesPayment;
} }
public Map<String, Object> undoDisbursal(final LoanScheduleGeneratorFactory loanScheduleFactory, public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final ApplicationCurrency applicationCurrency, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final List<Long> existingReversedTransactionIds, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO); validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO);
@@ -2254,8 +2333,7 @@ public class Loan extends AbstractPersistable<Long> {
// clear off actual disbusrement date so schedule regeneration // clear off actual disbusrement date so schedule regeneration
// uses expected date. // uses expected date.
regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, regenerateRepaymentSchedule(scheduleGeneratorDTO);
isHolidayEnabled, holidays, workingDays);
if (isDisbueseAmtChanged) { if (isDisbueseAmtChanged) {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
} }
@@ -2298,38 +2376,35 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTermVariations.clear(); this.loanTermVariations.clear();
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges()); wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
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<Holiday> holidays, final WorkingDays workingDays, final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
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);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate()); waiveInterestTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, loanLifecycleStateMachine, null, scheduleGeneratorDTO);
holidays, workingDays, calendarInstanceForInterestRecalculation);
return changedTransactionDetail; return changedTransactionDetail;
} }
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, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
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) {
@@ -2338,31 +2413,36 @@ public class Loan extends AbstractPersistable<Long> {
event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER; event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER;
} }
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateAccountStatus(event); validateAccountStatus(event);
validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate()); validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(event, repaymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, loanLifecycleStateMachine, null, scheduleGeneratorDTO);
holidays, workingDays, calendarInstanceForInterestRecalculation);
return changedTransactionDetail; return changedTransactionDetail;
} }
public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine, public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
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 HolidayDetailDTO holidayDetailDTO, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
final boolean allowTransactionsOnNonWorkingDay, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT); validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate()); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays); validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_CHARGE_PAYMENT,
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) { if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) {
final String errorMessage = "The date on which a loan charge paid cannot be in the future."; final String errorMessage = "The date on which a loan charge paid cannot be in the future.";
@@ -2415,9 +2495,7 @@ 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 ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
@@ -2516,12 +2594,12 @@ public class Loan extends AbstractPersistable<Long> {
} }
if (reprocess) { if (reprocess) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} }
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(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
} }
@@ -2607,6 +2685,18 @@ public class Loan extends AbstractPersistable<Long> {
return repaymentsOrWaivers; return repaymentsOrWaivers;
} }
private List<LoanTransaction> retreiveListOfAccrualTransactions() {
final List<LoanTransaction> transactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isNotReversed() && transaction.isAccrual()) {
transactions.add(transaction);
}
}
final LoanTransactionComparator transactionComparator = new LoanTransactionComparator();
Collections.sort(transactions, transactionComparator);
return transactions;
}
private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) { private void doPostLoanTransactionChecks(final LocalDate transactionDate, final LoanLifecycleStateMachine loanLifecycleStateMachine) {
if (isOverPaid()) { if (isOverPaid()) {
@@ -2767,13 +2857,13 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail, public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail,
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 ScheduleGeneratorDTO scheduleGeneratorDTO) {
final boolean isHolidayEnabled, final boolean allowTransactionsOnNonWorkingDay,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays); HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay); validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
ChangedTransactionDetail changedTransactionDetail = null; ChangedTransactionDetail changedTransactionDetail = null;
@@ -2803,17 +2893,14 @@ 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, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, transactionForAdjustment, scheduleGeneratorDTO);
holidays, workingDays, calendarInstanceForInterestRecalculation);
} }
return changedTransactionDetail; return changedTransactionDetail;
} }
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds, public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean isHolidayEnabled, final List<Holiday> holidays, final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
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());
@@ -2825,13 +2912,11 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} }
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges()); charges(), getLastUserTransactionForChargeCalc());
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
return changedTransactionDetail; return changedTransactionDetail;
} }
@@ -2893,15 +2978,12 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
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 ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
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(scheduleGeneratorDTO,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, loanRepaymentScheduleTransactionProcessor);
calendarInstanceForInterestRecalculation);
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING); validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
@@ -2960,25 +3042,20 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail; return changedTransactionDetail;
} }
private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory, private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
final List<Holiday> holidays, final WorkingDays workingDays,
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(scheduleGeneratorDTO);
workingDays);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} }
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(),
getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue()); this.loanTransactions.add(mapEntry.getValue());
@@ -3003,9 +3080,7 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine, public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
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 ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
validateAccountStatus(LoanEvent.LOAN_CLOSED); validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -3032,9 +3107,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(scheduleGeneratorDTO,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, loanRepaymentScheduleTransactionProcessor);
calendarInstanceForInterestRecalculation);
LoanTransaction loanTransaction = null; LoanTransaction loanTransaction = null;
if (isOpen()) { if (isOpen()) {
@@ -3596,7 +3670,8 @@ public class Loan extends AbstractPersistable<Long> {
final List<Map<String, Object>> newLoanTransactions = new ArrayList<>(); final List<Map<String, Object>> newLoanTransactions = new ArrayList<>();
for (final LoanTransaction transaction : this.loanTransactions) { for (final LoanTransaction transaction : this.loanTransactions) {
if (transaction.isReversed() && !existingReversedTransactionIds.contains(transaction.getId())) { if (transaction.isReversed() && existingTransactionIds.contains(transaction.getId())
&& !existingReversedTransactionIds.contains(transaction.getId())) {
newLoanTransactions.add(transaction.toMapData(currencyData)); newLoanTransactions.add(transaction.toMapData(currencyData));
} else if (!existingTransactionIds.contains(transaction.getId())) { } else if (!existingTransactionIds.contains(transaction.getId())) {
newLoanTransactions.add(transaction.toMapData(currencyData)); newLoanTransactions.add(transaction.toMapData(currencyData));
@@ -3621,10 +3696,13 @@ public class Loan extends AbstractPersistable<Long> {
if (receivableInterest.isLessThanZero()) { if (receivableInterest.isLessThanZero()) {
receivableInterest = receivableInterest.zero(); receivableInterest = receivableInterest.zero();
} }
/* if (transaction.getTransactionDate().isAfter(tillDate) && transaction.isAccrual()) { /*
final String errorMessage = "The date on which a loan is interest waived cannot be in after accrual transactions."; * if (transaction.getTransactionDate().isAfter(tillDate) &&
throw new InvalidLoanStateTransitionException("waive", "cannot.be.after.accrual.date", errorMessage, tillDate); * transaction.isAccrual()) { final String errorMessage =
}*/ * "The date on which a loan is interest waived cannot be in after accrual transactions."
* ; throw new InvalidLoanStateTransitionException("waive",
* "cannot.be.after.accrual.date", errorMessage, tillDate); }
*/
} }
return receivableInterest; return receivableInterest;
} }
@@ -3902,6 +3980,57 @@ public class Loan extends AbstractPersistable<Long> {
} }
} }
private void validateActivityNotBeforeLastTransactionDateForInterestRecalculation(final LoanEvent event, final LocalDate activityDate) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; }
LocalDate lastTransactionDate = getLastUserTransactionDate();
final LocalDate clientOfficeJoiningDate = this.client.getOfficeJoiningLocalDate();
if (lastTransactionDate.isAfter(activityDate)) {
String errorMessage = null;
String action = null;
String postfix = null;
switch (event) {
case LOAN_REPAYMENT_OR_WAIVER:
errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date";
action = "repayment.or.waiver";
postfix = "cannot.be.made.before.last.transaction.date";
break;
case WRITE_OFF_OUTSTANDING:
errorMessage = "The date on which a write off is made cannot be earlier than last transaction date";
action = "writeoff";
postfix = "cannot.be.made.before.last.transaction.date";
break;
case LOAN_CHARGE_PAYMENT:
errorMessage = "The date on which a charge payment is made cannot be earlier than last transaction date";
action = "charge.payment";
postfix = "cannot.be.made.before.last.transaction.date";
break;
default:
break;
}
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, clientOfficeJoiningDate);
}
}
private LocalDate getLastUserTransactionDate() {
LocalDate currentTransactionDate = getDisbursementDate();
for (final LoanTransaction previousTransaction : this.loanTransactions) {
if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) {
if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) {
currentTransactionDate = previousTransaction.getTransactionDate();
}
}
}
return currentTransactionDate;
}
public LocalDate getLastUserTransactionForChargeCalc() {
LocalDate lastTransaction = getDisbursementDate();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
lastTransaction = getLastUserTransactionDate();
}
return lastTransaction;
}
public Set<LoanCharge> charges() { public Set<LoanCharge> charges() {
Set<LoanCharge> loanCharges = new HashSet<>(); Set<LoanCharge> loanCharges = new HashSet<>();
if (this.charges != null) { if (this.charges != null) {
@@ -4081,9 +4210,7 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails, public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails,
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 ScheduleGeneratorDTO scheduleGeneratorDTO) {
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
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());
@@ -4095,12 +4222,9 @@ public class Loan extends AbstractPersistable<Long> {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null; regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
} else { } else {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, regenerateRepaymentSchedule(scheduleGeneratorDTO);
workingDays);
} }
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -4108,7 +4232,7 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges()); charges(), getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue()); this.loanTransactions.add(mapEntry.getValue());
@@ -4178,31 +4302,40 @@ public class Loan extends AbstractPersistable<Long> {
return maturityDate; return maturityDate;
} }
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final LoanScheduleGeneratorFactory loanScheduleFactory, public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds()); existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
LocalDate recalculateFrom = null; /*
List<LoanTransaction> loanTransactions = this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); * LocalDate recalculateFrom = null; List<LoanTransaction>
for (LoanTransaction loanTransaction : loanTransactions) { * loanTransactions =
if (recalculateFrom == null || loanTransaction.getTransactionDate().isAfter(recalculateFrom)) { * this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); for
recalculateFrom = loanTransaction.getTransactionDate(); * (LoanTransaction loanTransaction : loanTransactions) { if
} * (recalculateFrom == null ||
} * loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
* recalculateFrom = loanTransaction.getTransactionDate(); } }
* generatorDTO.setRecalculateFrom(recalculateFrom);
*/
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency, regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
calendarInstanceForInterestRecalculation, recalculateFrom);
} }
return processTransactions();
}
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
return processTransactions();
}
private ChangedTransactionDetail processTransactions() {
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();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges()); charges(), getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) { for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this); mapEntry.getValue().updateLoan(this);
} }
@@ -4216,28 +4349,38 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values()); this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
return changedTransactionDetail;
return changedTransactionDetail;
} }
private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory, private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
final LoanScheduleModel loanSchedule = getRecalculatedSchedule(loanScheduleFactory, applicationCurrency, LocalDate lastTransactionDate = getLastUserTransactionDate();
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, generatorDTO.setLastTransactionDate(lastTransactionDate);
recalculateFrom); final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO);
if (loanSchedule == null) { return; } if (loanSchedule == null) { return; }
updateLoanSchedule(loanSchedule); updateLoanSchedule(loanSchedule);
LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
final List<LoanTransaction> copyTransactions = new ArrayList<>();
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
}
loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(getDisbursementDate(), copyTransactions,
getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
Set<LoanCharge> charges = this.charges(); Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) { for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) { if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) { updateOverdueScheduleInstallment(loanCharge);
recalculateLoanCharge(loanCharge); if (loanCharge.getDueLocalDate() == null || (!lastRepaymentDate.isBefore(loanCharge.getDueLocalDate()))) {
loanCharge.updateWaivedAmount(getCurrency()); if (!loanCharge.isWaived() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod());
loanCharge.updateWaivedAmount(getCurrency());
}
} else { } else {
loanCharge.setActive(false); loanCharge.setActive(false);
} }
@@ -4246,28 +4389,25 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields(); updateLoanSummaryDerivedFields();
} }
private LoanScheduleModel getRecalculatedSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return null; } if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; }
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod(); final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod); final LoanScheduleGenerator loanScheduleGenerator = generatorDTO.getLoanScheduleFactory().create(interestMethod);
final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(applicationCurrency, final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(),
calculatedRepaymentsStartingFromDate, calendarInstanceForInterestRecalculation); generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy); .determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(),
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays, generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor, loanRepaymentScheduleTransactionProcessor, this.repaymentScheduleInstallments, generatorDTO.getRecalculateFrom(),
this.repaymentScheduleInstallments, recalculateFrom); generatorDTO.getLastTransactionDate(), generatorDTO.getPenaltyWaitPeriod());
return loanSchedule; return loanSchedule;
} }
@@ -4576,4 +4716,12 @@ public class Loan extends AbstractPersistable<Long> {
this.termFrequency = termFrequency; this.termFrequency = termFrequency;
} }
} }
public boolean isFeeCompoundingEnabledForInterestRecalculation() {
boolean isEnabled = false;
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
isEnabled = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled();
}
return isEnabled;
}
} }
@@ -20,6 +20,7 @@ import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomain
import org.mifosplatform.infrastructure.core.data.ApiParameterError; import org.mifosplatform.infrastructure.core.data.ApiParameterError;
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder; import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder; import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException; import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.holiday.domain.Holiday; import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.domain.HolidayRepository; import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
@@ -43,7 +44,9 @@ import org.mifosplatform.portfolio.client.exception.ClientNotActiveException;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
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.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.service.LoanAccrualWritePlatformService; import org.mifosplatform.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
@@ -149,6 +152,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
ApplicationCurrency applicationCurrency = null; ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null; LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate recalculateDueDateChargesFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -161,12 +167,17 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
calendarInstance); calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay);
final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
recalculateDueDateChargesFrom);
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction, final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory, scheduleGeneratorDTO);
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
@@ -259,17 +270,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail, final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail,
transactionDate, txnExternalId, loanTransactionType); transactionDate, txnExternalId, loanTransactionType);
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
if (loanTransactionType.isRepaymentAtDisbursement()) { if (loanTransactionType.isRepaymentAtDisbursement()) {
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds); loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
} else { } else {
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
transactionDate.toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay);
loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, loan.makeChargePayment(chargeId, defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction, holidayDetailDTO, newPaymentTransaction, installmentNumber);
installmentNumber);
} }
saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction); saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -422,7 +437,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override @Override
public void recalculateAccruals(Loan loan) { public void recalculateAccruals(Loan loan) {
LocalDate accruedTill = loan.getAccruedTill(); LocalDate accruedTill = loan.getAccruedTill();
if (!loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || accruedTill == null || loan.isAccrualsPresent()) { return; } if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled()
|| accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return; }
Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>(); Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>();
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
Long loanId = loan.getId(); Long loanId = loan.getId();
@@ -436,9 +452,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CurrencyData currencyData = applicationCurrency.toData(); CurrencyData currencyData = applicationCurrency.toData();
Set<LoanCharge> loanCharges = loan.charges(); Set<LoanCharge> loanCharges = loan.charges();
BigDecimal accruedInterestIncome = null;
BigDecimal accruedFeeIncome = null;
BigDecimal accruedPenaltyIncome = null;
for (LoanRepaymentScheduleInstallment installment : installments) { for (LoanRepaymentScheduleInstallment installment : installments) {
if (!accruedTill.isBefore(installment.getDueDate()) if (!accruedTill.isBefore(installment.getDueDate())
@@ -463,7 +476,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(), accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(),
installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment
.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(), .getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(),
accruedInterestIncome, accruedFeeIncome, accruedPenaltyIncome, currencyData, interestCalculatedFrom, installment installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(), installment
.getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom, installment
.getInterestWaived(currency).getAmount()); .getInterestWaived(currency).getAmount());
loanScheduleAccrualDatas.add(accrualData); loanScheduleAccrualDatas.add(accrualData);
@@ -471,7 +485,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
} }
if (!loanScheduleAccrualDatas.isEmpty()) { if (!loanScheduleAccrualDatas.isEmpty()) {
this.accrualWritePlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); String error = this.accrualWritePlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas);
if (error.length() > 0) {
String globalisationMessageCode = "error.msg.accrual.exception";
throw new GeneralPlatformDomainRuleException(globalisationMessageCode, error, error);
}
} }
} }
@@ -879,4 +879,8 @@ public class LoanCharge extends AbstractPersistable<Long> {
} }
} }
public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
return this.overdueInstallmentCharge;
}
} }
@@ -35,6 +35,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;
@@ -24,19 +24,27 @@ public class LoanOverdueInstallmentCharge extends AbstractPersistable<Long> {
@ManyToOne(optional = false) @ManyToOne(optional = false)
@JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false) @JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment installment; private LoanRepaymentScheduleInstallment installment;
@Column(name="frequency_number") @Column(name = "frequency_number")
private Integer frequencyNumber; private Integer frequencyNumber;
public LoanOverdueInstallmentCharge() {
public LoanOverdueInstallmentCharge(){
} }
public LoanOverdueInstallmentCharge(final LoanCharge loanCharge,final LoanRepaymentScheduleInstallment installment, final Integer frequencyNumber){ public LoanOverdueInstallmentCharge(final LoanCharge loanCharge, final LoanRepaymentScheduleInstallment installment,
final Integer frequencyNumber) {
this.loancharge = loanCharge; this.loancharge = loanCharge;
this.installment = installment; this.installment = installment;
this.frequencyNumber = frequencyNumber; this.frequencyNumber = frequencyNumber;
} }
public void updateLoanRepaymentScheduleInstallment(LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment) {
this.installment = loanRepaymentScheduleInstallment;
}
public LoanRepaymentScheduleInstallment getInstallment() {
return this.installment;
}
} }
@@ -65,7 +65,7 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true) @Column(name = "accrual_interest_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal interestAccrued; private BigDecimal interestAccrued;
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true) @Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesCharged; private BigDecimal feeChargesCharged;
@@ -78,6 +78,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true) @Column(name = "fee_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeChargesWaived; private BigDecimal feeChargesWaived;
@Column(name = "accrual_fee_charges_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal feeAccrued;
@Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true) @Column(name = "penalty_charges_amount", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyCharges; private BigDecimal penaltyCharges;
@@ -90,6 +93,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
@Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true) @Column(name = "penalty_charges_waived_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyChargesWaived; private BigDecimal penaltyChargesWaived;
@Column(name = "accrual_penalty_charges_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal penaltyAccrued;
@Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true) @Column(name = "total_paid_in_advance_derived", scale = 6, precision = 19, nullable = true)
private BigDecimal totalPaidInAdvance; private BigDecimal totalPaidInAdvance;
@@ -127,10 +133,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.obligationsMet = false; this.obligationsMet = false;
this.recalculatedInterestComponent = recalculatedInterestComponent; this.recalculatedInterestComponent = recalculatedInterestComponent;
} }
public LoanRepaymentScheduleInstallment(final Loan loan) { public LoanRepaymentScheduleInstallment(final Loan loan) {
this.loan = loan; this.loan = loan;
this.installmentNumber = null; this.installmentNumber = null;
this.fromDate = null; this.fromDate = null;
this.dueDate = null; this.dueDate = null;
this.obligationsMet = false; this.obligationsMet = false;
@@ -204,6 +210,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getInterestCharged(currency).minus(interestAccountedFor); return getInterestCharged(currency).minus(interestAccountedFor);
} }
public Money getInterestAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.interestAccrued);
}
public Money getFeeChargesCharged(final MonetaryCurrency currency) { public Money getFeeChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesCharged); return Money.of(currency, this.feeChargesCharged);
} }
@@ -226,6 +236,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getFeeChargesCharged(currency).minus(feeChargesAccountedFor); return getFeeChargesCharged(currency).minus(feeChargesAccountedFor);
} }
public Money getFeeAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.feeAccrued);
}
public Money getPenaltyChargesCharged(final MonetaryCurrency currency) { public Money getPenaltyChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyCharges); return Money.of(currency, this.penaltyCharges);
} }
@@ -248,6 +262,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor); return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor);
} }
public Money getPenaltyAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyAccrued);
}
public boolean isInterestDue(final MonetaryCurrency currency) { public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero(); return getInterestOutstanding(currency).isGreaterThanZero();
} }
@@ -264,10 +282,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void updateLoan(final Loan loan) { public void updateLoan(final Loan loan) {
this.loan = loan; this.loan = loan;
} }
public boolean isPartlyPaid() { public boolean isPartlyPaid() {
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null);
|| this.principalCompleted != null);
} }
public boolean isObligationsMet() { public boolean isObligationsMet() {
@@ -507,6 +524,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount()); this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
} }
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
this.interestAccrued = defaultToNullIfZero(interest.getAmount());
this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount());
this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
}
public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) { public void updateDerivedFields(final MonetaryCurrency currency, final LocalDate actualDisbursementDate) {
if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) { if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
this.obligationsMet = true; this.obligationsMet = true;
@@ -569,59 +592,57 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) { public void setRecalculatedInterestComponent(boolean recalculatedInterestComponent) {
this.recalculatedInterestComponent = recalculatedInterestComponent; this.recalculatedInterestComponent = recalculatedInterestComponent;
} }
public void updateInstallmentNumber(final Integer installmentNumber) {
if(installmentNumber != null) {
this.installmentNumber = installmentNumber;
}
}
public void updateInterestCharged(final BigDecimal interestCharged) {
this.interestCharged = interestCharged;
}
public void updateObligationMet(final Boolean obligationMet) {
this.obligationsMet = obligationMet;
}
public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) {
this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null;
}
public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) {
this.interestWrittenOff = interestWrittenOff;
}
public void updatePrincipal(final BigDecimal principal) {
this.principal = principal;
}
public static Comparator<LoanRepaymentScheduleInstallment> installmentNumberComparator
= new Comparator<LoanRepaymentScheduleInstallment>() {
@Override public void updateInstallmentNumber(final Integer installmentNumber) {
public int compare(LoanRepaymentScheduleInstallment arg0, if (installmentNumber != null) {
LoanRepaymentScheduleInstallment arg1) { this.installmentNumber = installmentNumber;
}
return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber()); }
}
public void updateInterestCharged(final BigDecimal interestCharged) {
this.interestCharged = interestCharged;
}
public void updateObligationMet(final Boolean obligationMet) {
this.obligationsMet = obligationMet;
}
public void updateObligationMetOnDate(final LocalDate obligationsMetOnDate) {
this.obligationsMetOnDate = (obligationsMetOnDate != null) ? obligationsMetOnDate.toDate() : null;
}
public void updateInterestWrittenOff(final BigDecimal interestWrittenOff) {
this.interestWrittenOff = interestWrittenOff;
}
public void updatePrincipal(final BigDecimal principal) {
this.principal = principal;
}
public static Comparator<LoanRepaymentScheduleInstallment> installmentNumberComparator = new Comparator<LoanRepaymentScheduleInstallment>() {
@Override
public int compare(LoanRepaymentScheduleInstallment arg0, LoanRepaymentScheduleInstallment arg1) {
return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
}
}; };
public BigDecimal getTotalPaidInAdvance() { public BigDecimal getTotalPaidInAdvance() {
return this.totalPaidInAdvance; return this.totalPaidInAdvance;
} }
public BigDecimal getTotalPaidLate() { public BigDecimal getTotalPaidLate() {
return this.totalPaidLate; return this.totalPaidLate;
} }
public LocalDate getObligationsMetOnDate() { public LocalDate getObligationsMetOnDate() {
LocalDate obligationsMetOnDate = null; LocalDate obligationsMetOnDate = null;
if(this.obligationsMetOnDate != null) { if (this.obligationsMetOnDate != null) {
obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate); obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate);
} }
return obligationsMetOnDate; return obligationsMetOnDate;
} }
} }
@@ -20,24 +20,29 @@ import org.mifosplatform.organisation.monetary.domain.Money;
public class LoanRepaymentScheduleProcessingWrapper { public class LoanRepaymentScheduleProcessingWrapper {
public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate, public void reprocess(final MonetaryCurrency currency, final LocalDate disbursementDate,
final List<LoanRepaymentScheduleInstallment> repaymentPeriods, final Set<LoanCharge> loanCharges) { final List<LoanRepaymentScheduleInstallment> repaymentPeriods, final Set<LoanCharge> loanCharges,
final LocalDate lastTransactionDate) {
Money totalInterest = Money.zero(currency); Money totalInterest = Money.zero(currency);
Money totalPrincipal = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) { for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) {
totalInterest = totalInterest.plus(installment.getInterestCharged(currency)); totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
} }
LocalDate startDate = disbursementDate; LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) { for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges, final Money feeChargesDueForRepaymentPeriod = cumulativeFeeChargesDueWithin(startDate, period.getDueDate(), loanCharges,
currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest, !period.isRecalculatedInterestComponent(),
lastTransactionDate);
final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges, final Money feeChargesWaivedForRepaymentPeriod = cumulativeFeeChargesWaivedWithin(startDate, period.getDueDate(), loanCharges,
currency, !period.isRecalculatedInterestComponent()); currency, !period.isRecalculatedInterestComponent());
final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(), final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
loanCharges, currency, !period.isRecalculatedInterestComponent()); loanCharges, currency, !period.isRecalculatedInterestComponent());
final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(), final Money penaltyChargesDueForRepaymentPeriod = cumulativePenaltyChargesDueWithin(startDate, period.getDueDate(),
loanCharges, currency, period, repaymentPeriods.size(), totalInterest, !period.isRecalculatedInterestComponent()); loanCharges, currency, period, repaymentPeriods.size(), totalPrincipal, totalInterest,
!period.isRecalculatedInterestComponent(), lastTransactionDate);
final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(), final Money penaltyChargesWaivedForRepaymentPeriod = cumulativePenaltyChargesWaivedWithin(startDate, period.getDueDate(),
loanCharges, currency, !period.isRecalculatedInterestComponent()); loanCharges, currency, !period.isRecalculatedInterestComponent());
final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate, final Money penaltyChargesWrittenOffForRepaymentPeriod = cumulativePenaltyChargesWrittenOffWithin(startDate,
@@ -53,7 +58,8 @@ public class LoanRepaymentScheduleProcessingWrapper {
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges, private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, int numberOfRepayments, final MonetaryCurrency monetaryCurrency, LoanRepaymentScheduleInstallment period, int numberOfRepayments,
final Money totalInterest, boolean isInstallmentChargeApplicable) { final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable,
final LocalDate lastTransactionDate) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
@@ -80,14 +86,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount()); cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO; BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getPrincpal().getAmount()).add(totalInterest.getAmount()); amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterest.getAmount()); amount = amount.add(totalInterest.getAmount());
} else { } else {
amount = amount.add(period.getLoan().getPrincpal().getAmount()); amount = amount.add(totalPrincipal.getAmount());
} }
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt); cumulative = cumulative.plus(loanChargeAmt);
@@ -144,7 +151,8 @@ public class LoanRepaymentScheduleProcessingWrapper {
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period, final Set<LoanCharge> loanCharges, final MonetaryCurrency currency, LoanRepaymentScheduleInstallment period,
int numberOfRepayments, Money totalInterest, boolean isInstallmentChargeApplicable) { int numberOfRepayments, final Money totalPrincipal, final Money totalInterest, boolean isInstallmentChargeApplicable,
final LocalDate lastTransactionDate) {
Money cumulative = Money.zero(currency); Money cumulative = Money.zero(currency);
@@ -171,14 +179,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount()); cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO; BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(period.getLoan().getPrincpal().getAmount()).add(totalInterest.getAmount()); amount = amount.add(totalPrincipal.getAmount()).add(totalInterest.getAmount());
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) { } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
amount = amount.add(totalInterest.getAmount()); amount = amount.add(totalInterest.getAmount());
} else { } else {
amount = amount.add(period.getLoan().getPrincpal().getAmount()); amount = amount.add(totalPrincipal.getAmount());
} }
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100)); BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt); cumulative = cumulative.plus(loanChargeAmt);
@@ -241,7 +241,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge) { final Money feeChargesWaived, final Money penaltyChargesWaived, final Money unrecognizedCharge) {
final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate, final LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
null); null);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived,unrecognizedCharge); waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
return waiver; return waiver;
} }
@@ -304,7 +304,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
updateChargesComponents(feeCharges, penaltyCharges); updateChargesComponents(feeCharges, penaltyCharges);
} }
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) { public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
final MonetaryCurrency currency = feeCharges.getCurrency(); final MonetaryCurrency currency = feeCharges.getCurrency();
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount()); this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount()); this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
@@ -322,7 +322,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount()); this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount()); this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
} }
public void adjustInterestComponent(final MonetaryCurrency currency) { public void adjustInterestComponent(final MonetaryCurrency currency) {
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount()); this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount());
} }
@@ -503,7 +503,8 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
} }
return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData, return new LoanTransactionData(getId(), this.office.getId(), this.office.getName(), transactionType, paymentDetailData,
currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion, currencyData, getTransactionDate(), this.amount, this.principalPortion, this.interestPortion, this.feeChargesPortion,
this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null, outstandingLoanBalance, unrecognizedIncomePortion); this.penaltyChargesPortion, this.overPaymentPortion, this.externalId, transfer, null, outstandingLoanBalance,
unrecognizedIncomePortion);
} }
public Map<String, Object> toMapData(final CurrencyData currencyData) { public Map<String, Object> toMapData(final CurrencyData currencyData) {
@@ -48,7 +48,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
@Override @Override
public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate, public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency, final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) { final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final LocalDate recalculateChargesFrom) {
final boolean reprocessCharges = true;
return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom,
reprocessCharges);
}
private ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges,
final LocalDate recalculateChargesFrom, boolean reprocessCharges) {
if (charges != null) { if (charges != null) {
for (final LoanCharge loanCharge : charges) { for (final LoanCharge loanCharge : charges) {
@@ -65,8 +74,10 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
// re-process loan charges over repayment periods (picking up on waived // re-process loan charges over repayment periods (picking up on waived
// loan charges) // loan charges)
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper(); if (reprocessCharges) {
wrapper.reprocess(currency, disbursementDate, installments, charges); final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(currency, disbursementDate, installments, charges, recalculateChargesFrom);
}
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail(); final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>(); final List<LoanTransaction> transactionstoBeProcessed = new ArrayList<>();
@@ -199,7 +210,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess, final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
final boolean isFeeCharge) { final boolean isFeeCharge) {
// to. // to.
if(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()){ if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents(); loanTransaction.resetDerivedComponents();
} }
Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess); Money transactionAmountUnprocessed = processTransaction(loanTransaction, currency, installments, chargeAmountToProcess);
@@ -500,4 +511,13 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
} }
} }
@Override
public ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(final LocalDate disbursementDate,
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final LocalDate recalculateChargesFrom) {
final boolean reprocessCharges = false;
return handleTransaction(disbursementDate, transactionsPostDisbursement, currency, installments, charges, recalculateChargesFrom,
reprocessCharges);
}
} }
@@ -23,7 +23,8 @@ public interface LoanRepaymentScheduleTransactionProcessor {
Set<LoanCharge> charges); Set<LoanCharge> charges);
ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers, ChangedTransactionDetail handleTransaction(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges); MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges,
LocalDate recalculateChargesFrom);
void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency, void handleWriteOff(LoanTransaction loanTransaction, MonetaryCurrency loanCurrency,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
@@ -31,13 +32,18 @@ public interface LoanRepaymentScheduleTransactionProcessor {
List<RecalculationDetail> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency, List<RecalculationDetail> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment, List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment,
Map<LocalDate, LocalDate> recalculationDates, LoanTransaction preCloseTransaction); Map<LocalDate, LocalDate> recalculationDates, LoanTransaction preCloseTransaction);
/** /**
* Used in interest recalculation to introduce new interest only installment. * Used in interest recalculation to introduce new interest only
* installment.
*/ */
boolean isInterestFirstRepaymentScheduleTransactionProcessor(); boolean isInterestFirstRepaymentScheduleTransactionProcessor();
void applyTransaction(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency, void applyTransaction(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments); List<LoanRepaymentScheduleInstallment> installments);
ChangedTransactionDetail populateDerivedFeildsWithoutReprocess(LocalDate disbursementDate,
List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, LocalDate recalculateChargesFrom);
} }
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@@ -18,14 +19,13 @@ import java.util.TreeMap;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
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.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.portfolio.calendar.domain.CalendarInstance; 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.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
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;
@@ -49,22 +49,21 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator(); private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override @Override
public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
final List<Holiday> holidays, final WorkingDays workingDays) {
final List<RecalculationDetail> diffAmt = null; final List<RecalculationDetail> diffAmt = null;
final LocalDate prepayDate = null; final LocalDate prepayDate = null;
return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt, final LocalDate lastTransactionDate = loanApplicationTerms.getExpectedDisbursementDate();
prepayDate); return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, prepayDate, lastTransactionDate);
} }
private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency, private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, List<RecalculationDetail> diffAmt,
final List<Holiday> holidays, final WorkingDays workingDays, List<RecalculationDetail> diffAmt, LocalDate prepayDate) { final LocalDate prepayDate, final LocalDate lastTransactionDate) {
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
// 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, holidayDetailDTO);
workingDays);
loanApplicationTerms.updateLoanEndDate(loanEndDate); loanApplicationTerms.updateLoanEndDate(loanEndDate);
// 2. determine the total charges due at time of disbursement // 2. determine the total charges due at time of disbursement
@@ -144,7 +143,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
isFirstRepayment); isFirstRepayment);
isFirstRepayment = false; isFirstRepayment = false;
scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays); holidayDetailDTO);
scheduledDueDateAsPerFrequency = scheduledDueDate; scheduledDueDateAsPerFrequency = scheduledDueDate;
} else { } else {
recalculatedInterestComponent = true; recalculatedInterestComponent = true;
@@ -153,7 +152,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
loanApplicationTerms, isFirstRepayment); loanApplicationTerms, isFirstRepayment);
scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms, scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays); holidayDetailDTO);
} }
final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays(); final int daysInPeriod = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
@@ -488,10 +487,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
Money.of(currency, loanScheduleModelPeriod.interestDue()), null); Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed,
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(),
lastTransactionDate);
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(), Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed, loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed,
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent()); totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(),
lastTransactionDate);
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount()); totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount()); totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount()); loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
@@ -549,8 +550,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
@Override @Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final List<Holiday> holidays, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO) {
final WorkingDays workingDays) {
final Loan loan = loanRescheduleRequest.getLoan(); final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary(); final LoanSummary loanSummary = loan.getSummary();
@@ -696,8 +696,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// update the number of repayments // update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays, LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
workingDays);
loanApplicationTerms.updateLoanEndDate(loanEndDate); loanApplicationTerms.updateLoanEndDate(loanEndDate);
if (newInterestRate != null) { if (newInterestRate != null) {
@@ -739,7 +738,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms, adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays); holidayDetailDTO);
final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays(); final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate).getDays();
@@ -936,7 +935,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges, private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod,
final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments,
boolean isInstallmentChargeApplicable) { boolean isInstallmentChargeApplicable, final LocalDate lastTransactionDate) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
@@ -963,7 +962,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount()); cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO; BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
@@ -986,7 +986,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency, final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable) { final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments, boolean isInstallmentChargeApplicable,
final LocalDate lastTransactionDate) {
Money cumulative = Money.zero(monetaryCurrency); Money cumulative = Money.zero(monetaryCurrency);
@@ -1013,7 +1014,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount()); cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd) } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) { && loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO; BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) { if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount()); amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
@@ -1039,11 +1041,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* of over due installment should be collected before collecting principal ) * of over due installment should be collected before collecting principal )
*/ */
@Override @Override
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency, public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom) { final List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom, final LocalDate lastTransactionDate,
final int penaltyWaitPeriod) {
LoanScheduleModel loanScheduleModel = null; LoanScheduleModel loanScheduleModel = null;
List<LoanRepaymentScheduleInstallment> installments = previousSchedule; List<LoanRepaymentScheduleInstallment> installments = previousSchedule;
@@ -1064,8 +1066,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
installments.removeAll(removeInstallments); installments.removeAll(removeInstallments);
if (previousSchedule == null) { if (previousSchedule == null) {
loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO);
workingDays);
installments = retrieveRepaymentSchedule(loanScheduleModel); installments = retrieveRepaymentSchedule(loanScheduleModel);
} }
@@ -1082,10 +1083,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound); int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound);
List<LoanTransaction> processTransactions = processTransactions(transactions, processTransactionsForInterestCompound); List<LoanTransaction> processTransactions = processTransactions(transactions, processTransactionsForInterestCompound);
if (processTransactions.size() > 0) { if (processTransactions.size() > 0) {
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency,
loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, false, recalculationDetails, retainRecalculationDetails, false, installmentNumber, processTransactions,
installmentNumber, processTransactions, preCloseTransaction); preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
} }
recalculationDetails.retainAll(retainRecalculationDetails.values()); recalculationDetails.retainAll(retainRecalculationDetails.values());
processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound); processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound);
@@ -1097,10 +1098,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanTransaction> processTransactions = processTransactions(transactions, recalculateFrom); List<LoanTransaction> processTransactions = processTransactions(transactions, recalculateFrom);
if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor() if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()
&& processTransactions.size() > 0) { && processTransactions.size() > 0) {
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges, loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency,
loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, true, recalculationDetails, retainRecalculationDetails, true, installmentNumber, processTransactions,
installmentNumber, processTransactions, preCloseTransaction); preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
if (loanScheduleModel != null) { if (loanScheduleModel != null) {
installments = retrieveRepaymentSchedule(loanScheduleModel); installments = retrieveRepaymentSchedule(loanScheduleModel);
} }
@@ -1114,9 +1115,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
recalculationDetails.retainAll(retainRecalculationDetails.values()); recalculationDetails.retainAll(retainRecalculationDetails.values());
recalculationDetails.addAll(earlypayments); recalculationDetails.addAll(earlypayments);
loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, processTransactions,
processTransactions, installmentNumber, recalculationDetails, preCloseTransaction); installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
if (loanScheduleModel != null) { if (loanScheduleModel != null) {
installments = retrieveRepaymentSchedule(loanScheduleModel); installments = retrieveRepaymentSchedule(loanScheduleModel);
} }
@@ -1130,14 +1131,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
/** /**
* Method identifies interest only repayment periods * Method identifies interest only repayment periods
*/ */
private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final ApplicationCurrency applicationCurrency, private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
final List<Holiday> holidays, final WorkingDays workingDays,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel, final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
final List<RecalculationDetail> recalculationDetails, final Map<LocalDate, RecalculationDetail> retainRecalculationDetails, final List<RecalculationDetail> recalculationDetails, final Map<LocalDate, RecalculationDetail> retainRecalculationDetails,
boolean recalculate, int installmentNumber, List<LoanTransaction> processTransactions, LoanTransaction preCloseTransaction) { boolean recalculate, int installmentNumber, List<LoanTransaction> processTransactions, LoanTransaction preCloseTransaction,
final LocalDate lastTransactionDate, final int penaltyWaitPeriod, final List<LoanTransaction> actualTransactions) {
LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1); LoanTransaction loanTransaction = processTransactions.remove(processTransactions.size() - 1);
if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) { if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) {
processTransactions.add(loanTransaction); processTransactions.add(loanTransaction);
@@ -1187,10 +1188,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
recalculationDetails.add(recalculationDetail); recalculationDetails.add(recalculationDetail);
if (recalculate) { if (recalculate) {
recalculationDetails.retainAll(retainRecalculationDetails.values()); recalculationDetails.retainAll(retainRecalculationDetails.values());
loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments,
loanScheduleModel, installments, processTransactions, installmentNumber, recalculationDetails, processTransactions, installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate,
preCloseTransaction); penaltyWaitPeriod, actualTransactions);
} }
} }
@@ -1221,19 +1222,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* @param preCloseTransaction * @param preCloseTransaction
* TODO * TODO
*/ */
private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency, private LoanScheduleModel recalculateInstallment(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
final List<Holiday> holidays, final WorkingDays workingDays,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel, final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> processTransactions, final int skipRecalculation, List<LoanRepaymentScheduleInstallment> installments, List<LoanTransaction> processTransactions, final int skipRecalculation,
final List<RecalculationDetail> recalculationDetails, LoanTransaction preCloseTransaction) { final List<RecalculationDetail> recalculationDetails, LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate,
final int penaltyWaitPeriod, final List<LoanTransaction> actualTransactions) {
int processInstallmentsFrom = 0; int processInstallmentsFrom = 0;
Integer lastInstallmentNumber = previousSchedule.size(); Integer lastInstallmentNumber = previousSchedule.size();
while (processInstallmentsFrom < lastInstallmentNumber) { while (processInstallmentsFrom < lastInstallmentNumber) {
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges, RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor, processTransactions, loanRepaymentScheduleTransactionProcessor, processInstallmentsFrom, installments,
processInstallmentsFrom, installments, recalculationDetails, skipRecalculation, preCloseTransaction); recalculationDetails, skipRecalculation, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod,
actualTransactions);
processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber(); processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber();
if (recalculatedSchedule.getLoanScheduleModel() != null) { if (recalculatedSchedule.getLoanScheduleModel() != null) {
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel(); loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
@@ -1250,12 +1252,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* @param preCloseTransaction * @param preCloseTransaction
* TODO * TODO
*/ */
private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency, private RecalculatedSchedule recalculateInterest(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> recalculationDetails, final List<LoanRepaymentScheduleInstallment> installments, final List<RecalculationDetail> recalculationDetails,
final int skipRecalculation, final LoanTransaction preCloseTransaction) { final int skipRecalculation, final LoanTransaction preCloseTransaction, final LocalDate lastTransactionDate,
final int penaltyWaitPeriod, final List<LoanTransaction> actualTransactions) {
boolean processRecalculate = false; boolean processRecalculate = false;
int processedInstallmentNumber = installmentNumber; int processedInstallmentNumber = installmentNumber;
@@ -1282,15 +1284,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanTransaction> transactionsForInstallment = new ArrayList<>(); List<LoanTransaction> transactionsForInstallment = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>(); Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
isHolidayEnabled, holidays, workingDays); holidayDetailDTO);
for (LoanTransaction loanTransaction : transactions) { for (LoanTransaction loanTransaction : transactions) {
LocalDate loantransactionDate = loanTransaction.getTransactionDate(); LocalDate loantransactionDate = loanTransaction.getTransactionDate();
if (!loantransactionDate.isAfter(transactionsDate)) { if (!loantransactionDate.isAfter(transactionsDate)) {
transactionsForInstallment.add(loanTransaction); transactionsForInstallment.add(loanTransaction);
recalculationDates.put( recalculationDates.put(loantransactionDate,
loantransactionDate, getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO));
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
} }
} }
@@ -1321,15 +1321,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// this block is to identify late payment based on the rest // this block is to identify late payment based on the rest
// calculation date // calculation date
while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) { while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) {
LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
holidays, workingDays); LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, holidayDetailDTO);
LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
if (reduceStartDate) { if (reduceStartDate) {
startDate = startDate.minusDays(1); startDate = startDate.minusDays(1);
} }
applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency, applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency,
loanApplicationTerms, isHolidayEnabled, holidays, workingDays, installments, preCloseTransaction); loanApplicationTerms, holidayDetailDTO, installments, preCloseTransaction);
totalOutstanding = installment.getTotalOutstanding(currency); totalOutstanding = installment.getTotalOutstanding(currency);
if (totalOutstanding.isGreaterThanZero()) { if (totalOutstanding.isGreaterThanZero()) {
Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency); Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency);
@@ -1373,12 +1371,130 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (preCloseTransaction != null) { if (preCloseTransaction != null) {
prepayDate = preCloseTransaction.getTransactionDate(); prepayDate = preCloseTransaction.getTransactionDate();
} }
model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, model = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, prepayDate, lastTransactionDate);
recalculationDetails, prepayDate); model = updateOverDueCharges(loanCharges, model, lastTransactionDate, currency, loanRepaymentScheduleTransactionProcessor,
loanApplicationTerms.getExpectedDisbursementDate(), penaltyWaitPeriod, actualTransactions);
} }
return new RecalculatedSchedule(model, processedInstallmentNumber); return new RecalculatedSchedule(model, processedInstallmentNumber);
} }
private LoanScheduleModel updateOverDueCharges(final Set<LoanCharge> loanCharges, final LoanScheduleModel model,
LocalDate lastTransactionDate, final MonetaryCurrency currency,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate disbursementDate,
final int penaltyWaitPeriod, final List<LoanTransaction> transactions) {
Set<LoanCharge> chargesForUpdate = new HashSet<>();
LoanScheduleModel loanScheduleModel = model;
for (LoanCharge loanCharge : loanCharges) {
if (loanCharge.isActive() && loanCharge.isOverdueInstallmentCharge() && loanCharge.isPenaltyCharge()
&& loanCharge.getChargeCalculation().isPercentageBased() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
chargesForUpdate.add(loanCharge);
}
}
if (!chargesForUpdate.isEmpty()) {
LocalDate graceDate = LocalDate.now().minusDays(penaltyWaitPeriod);
Collection<LoanScheduleModelPeriod> periods = model.getPeriods();
BigDecimal totalPenaltyChargesCharged = model.getTotalPenaltyChargesCharged();
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
Money overdueChargeForInstallment = overDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), chargesForUpdate, currency);
totalPenaltyChargesCharged = totalPenaltyChargesCharged.subtract(overdueChargeForInstallment.getAmount());
BigDecimal feeChargesForInstallment = BigDecimal.ZERO;
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.negated().getAmount());
}
}
List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentSchedule(model);
List<LoanTransaction> loanTransactions = copyTransactions(transactions);
loanRepaymentScheduleTransactionProcessor.populateDerivedFeildsWithoutReprocess(disbursementDate, loanTransactions, currency,
installments, loanCharges, lastTransactionDate);
Map<Integer, LoanRepaymentScheduleInstallment> installmentMap = getRepaymentsAsMap(installments);
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
LoanRepaymentScheduleInstallment installment = installmentMap.get(loanScheduleModelPeriod.periodNumber());
Money overdueChargeForInstallment = Money.zero(currency);
overdueChargeForInstallment = cumulativeOverDuePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, installment.getPrincipalOutstanding(currency)
.getAmount(), installment.getInterestOutstanding(currency).getAmount(), graceDate.isAfter(installment
.getDueDate()));
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(overdueChargeForInstallment.getAmount());
BigDecimal feeChargesForInstallment = BigDecimal.ZERO;
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment, overdueChargeForInstallment.getAmount());
}
}
loanScheduleModel = LoanScheduleModel.withOverdueChargeUpdation(periods, model, totalPenaltyChargesCharged);
}
return loanScheduleModel;
}
private Map<Integer, LoanRepaymentScheduleInstallment> getRepaymentsAsMap(List<LoanRepaymentScheduleInstallment> installments) {
Map<Integer, LoanRepaymentScheduleInstallment> installmentMap = new HashMap<>();
for (LoanRepaymentScheduleInstallment installment : installments) {
installmentMap.put(installment.getInstallmentNumber(), installment);
}
return installmentMap;
}
private List<LoanTransaction> copyTransactions(List<LoanTransaction> transactions) {
List<LoanTransaction> loanTransactions = new ArrayList<>();
for (LoanTransaction loanTransaction : transactions) {
if (loanTransaction.isNotReversed() && !loanTransaction.isAccrual()) {
LoanTransaction transaction = LoanTransaction.copyTransactionProperties(loanTransaction);
loanTransactions.add(transaction);
}
}
return loanTransactions;
}
private Money cumulativeOverDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency, final BigDecimal principalOverdue,
final BigDecimal interestOverdue, final boolean recalculate) {
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
BigDecimal amount = BigDecimal.ZERO;
if (recalculate) {
switch (loanCharge.getChargeCalculation()) {
case PERCENT_OF_AMOUNT:
amount = amount.add(principalOverdue);
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
amount = amount.add(principalOverdue).add(interestOverdue);
break;
case PERCENT_OF_INTEREST:
amount = amount.add(interestOverdue);
break;
default:
break;
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
}
}
}
}
return cumulative;
}
private Money overDuePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
final MonetaryCurrency monetaryCurrency) {
Money cumulative = Money.zero(monetaryCurrency);
for (final LoanCharge loanCharge : loanCharges) {
if (loanCharge.isPenaltyCharge()) {
if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
}
}
}
return cumulative;
}
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentSchedule(LoanScheduleModel model) { private List<LoanRepaymentScheduleInstallment> retrieveRepaymentSchedule(LoanScheduleModel model) {
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>(); final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) { for (final LoanScheduleModelPeriod scheduledLoanInstallment : model.getPeriods()) {
@@ -1404,18 +1520,16 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private void applyRest(List<LoanTransaction> loanTransactions, LocalDate from, LocalDate to, private void applyRest(List<LoanTransaction> loanTransactions, LocalDate from, LocalDate to,
LoanRepaymentScheduleInstallment installment, LoanRepaymentScheduleInstallment installment,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency,
final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List<Holiday> holidays, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
final WorkingDays workingDays, final List<LoanRepaymentScheduleInstallment> installments, LoanTransaction preCloseTransaction) { final List<LoanRepaymentScheduleInstallment> installments, LoanTransaction preCloseTransaction) {
List<LoanTransaction> transactions = new ArrayList<>(); List<LoanTransaction> transactions = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>(); Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
for (LoanTransaction transaction : loanTransactions) { for (LoanTransaction transaction : loanTransactions) {
LocalDate loantransactionDate = transaction.getTransactionDate(); LocalDate loantransactionDate = transaction.getTransactionDate();
if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) { if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) {
transactions.add(transaction); transactions.add(transaction);
recalculationDates.put( recalculationDates.put(loantransactionDate,
loantransactionDate, getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO));
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
} }
} }
@@ -1424,11 +1538,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
} }
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms, private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) { final HolidayDetailDTO holidayDetailDTO) {
LocalDate nextScheduleDate = null; LocalDate nextScheduleDate = null;
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate, nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate,
loanApplicationTerms, isHolidayEnabled, holidays, workingDays); loanApplicationTerms, holidayDetailDTO);
} else { } else {
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate); nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
@@ -5,24 +5,20 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.util.List;
import org.joda.time.Days; import org.joda.time.Days;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.joda.time.Months; import org.joda.time.Months;
import org.joda.time.Weeks; import org.joda.time.Weeks;
import org.joda.time.Years; import org.joda.time.Years;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.service.HolidayUtil; import org.mifosplatform.organisation.holiday.service.HolidayUtil;
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.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override @Override
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
final List<Holiday> holidays, final WorkingDays workingDays) {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments(); final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
@@ -32,7 +28,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment); lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false; isFirstRepayment = false;
} }
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
return lastRepaymentDate; return lastRepaymentDate;
} }
@@ -52,22 +48,23 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override @Override
public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms, public LocalDate adjustRepaymentDate(final LocalDate dueRepaymentPeriodDate, final LoanApplicationTerms loanApplicationTerms,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) { final HolidayDetailDTO holidayDetailDTO) {
LocalDate adjustedDate = dueRepaymentPeriodDate; LocalDate adjustedDate = dueRepaymentPeriodDate;
/** /**
* Fix for https://mifosforge.jira.com/browse/MIFOSX-1357 * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
*/ */
// recursively check for the next working day. // recursively check for the next working day.
while (WorkingDaysUtil.isNonWorkingDay(workingDays, adjustedDate)) { while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDate)) {
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), adjustedDate); loanApplicationTerms.getRepaymentEvery(), adjustedDate);
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, workingDays); adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
holidayDetailDTO.getWorkingDays());
} }
if (isHolidayEnabled) { if (holidayDetailDTO.isHolidayEnabled()) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidays); adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
} }
return adjustedDate; return adjustedDate;
} }
@@ -161,7 +158,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override @Override
public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate,
LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays, WorkingDays workingDays) { LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate(); LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true; boolean isFirstRepayment = true;
@@ -169,7 +166,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment); generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false; isFirstRepayment = false;
} }
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays); generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
return generatedDate; return generatedDate;
} }
} }
@@ -110,7 +110,7 @@ public final class LoanApplicationTerms {
private BigDecimal maxOutstandingBalance; private BigDecimal maxOutstandingBalance;
private List<LoanTermVariationsData> emiAmountVariations; private List<LoanTermVariationsData> emiAmountVariations;
private Money totalInterestDue; private Money totalInterestDue;
private final DaysInMonthType daysInMonthType; private final DaysInMonthType daysInMonthType;
@@ -505,7 +505,7 @@ public final class LoanApplicationTerms {
final int periodsElapsed = 0; final int periodsElapsed = 0;
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed); final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments; double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment)); return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
} }
@@ -521,16 +521,16 @@ public final class LoanApplicationTerms {
case FLAT: case FLAT:
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
break; break;
case DECLINING_BALANCE: case DECLINING_BALANCE:
break; break;
case INVALID: case INVALID:
break; break;
} }
if(this.totalInterestDue != null) { if (this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue; totalInterestDue = this.totalInterestDue;
} }
return totalInterestDue; return totalInterestDue;
@@ -544,7 +544,7 @@ public final class LoanApplicationTerms {
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm();
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
.multiply(loanTermFrequencyBigDecimal); .multiply(loanTermFrequencyBigDecimal);
} }
@@ -1078,59 +1078,62 @@ public final class LoanApplicationTerms {
return numberOfPeriods; return numberOfPeriods;
} }
public RecalculationFrequencyType getRecalculationFrequencyType() { public RecalculationFrequencyType getRecalculationFrequencyType() {
return this.recalculationFrequencyType; return this.recalculationFrequencyType;
} }
public void updateNumberOfRepayments(final Integer numberOfRepayments) { public void updateNumberOfRepayments(final Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments; this.numberOfRepayments = numberOfRepayments;
} }
public void updatePrincipalGrace(final Integer principalGrace) { public void updatePrincipalGrace(final Integer principalGrace) {
this.principalGrace = principalGrace; this.principalGrace = principalGrace;
} }
public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { public void updateInterestPaymentGrace(final Integer interestPaymentGrace) {
this.interestPaymentGrace = interestPaymentGrace; this.interestPaymentGrace = interestPaymentGrace;
} }
public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) {
if(interestRatePerPeriod != null) { if (interestRatePerPeriod != null) {
this.interestRatePerPeriod = interestRatePerPeriod; this.interestRatePerPeriod = interestRatePerPeriod;
} }
} }
public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) { public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) {
if(annualNominalInterestRate != null) { if (annualNominalInterestRate != null) {
this.annualNominalInterestRate = annualNominalInterestRate; this.annualNominalInterestRate = annualNominalInterestRate;
} }
} }
public BigDecimal getInterestRatePerPeriod() { public BigDecimal getInterestRatePerPeriod() {
return this.interestRatePerPeriod; return this.interestRatePerPeriod;
} }
public BigDecimal getAnnualNominalInterestRate() { public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate; return this.annualNominalInterestRate;
} }
public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) {
if(interestChargedFromDate != null) { if (interestChargedFromDate != null) {
this.interestChargedFromDate = interestChargedFromDate; this.interestChargedFromDate = interestChargedFromDate;
} }
} }
public void updateLoanTermFrequency(Integer loanTermFrequency) { public void updateLoanTermFrequency(Integer loanTermFrequency) {
if(loanTermFrequency != null) { if (loanTermFrequency != null) {
this.loanTermFrequency = loanTermFrequency; this.loanTermFrequency = loanTermFrequency;
} }
} }
public void updateTotalInterestDue(Money totalInterestDue) { public void updateTotalInterestDue(Money totalInterestDue) {
if(totalInterestDue != null) { if (totalInterestDue != null) {
this.totalInterestDue = totalInterestDue; this.totalInterestDue = totalInterestDue;
} }
}
public ApplicationCurrency getApplicationCurrency() {
return this.currency;
} }
} }
@@ -10,10 +10,9 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
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.MonetaryCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
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;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -23,19 +22,19 @@ import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanResched
public interface LoanScheduleGenerator { public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms, LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays); final HolidayDetailDTO holidayDetailDTO);
LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency, LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions,
WorkingDays workingDays, List<LoanTransaction> transactions,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom); List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom, LocalDate lastTransactionDate,
int penaltyWaitPeriod);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges); MonetaryCurrency currency, LocalDate onDate, LocalDate interestChargedFromLocalDate, LoanApplicationTerms loanApplicationTerms,
MathContext mc, Set<LoanCharge> charges);
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final List<Holiday> holidays, final WorkingDays workingDays); final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO);
} }
@@ -42,6 +42,15 @@ public final class LoanScheduleModel {
totalOutstanding); totalOutstanding);
} }
public static LoanScheduleModel withOverdueChargeUpdation(final Collection<LoanScheduleModelPeriod> periods,
final LoanScheduleModel loanScheduleModel, final BigDecimal totalPenaltyChargesCharged) {
return new LoanScheduleModel(periods, loanScheduleModel.applicationCurrency, loanScheduleModel.loanTermInDays,
loanScheduleModel.totalPrincipalDisbursed, loanScheduleModel.totalPrincipalExpected, loanScheduleModel.totalPrincipalPaid,
loanScheduleModel.totalInterestCharged, loanScheduleModel.totalFeeChargesCharged, totalPenaltyChargesCharged,
loanScheduleModel.totalRepaymentExpected, loanScheduleModel.totalOutstanding);
}
private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency, private LoanScheduleModel(final Collection<LoanScheduleModelPeriod> periods, final ApplicationCurrency applicationCurrency,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected, final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged, final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
@@ -85,4 +94,9 @@ public final class LoanScheduleModel {
public Collection<LoanScheduleModelPeriod> getPeriods() { public Collection<LoanScheduleModelPeriod> getPeriods() {
return this.periods; return this.periods;
} }
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
} }
@@ -5,30 +5,25 @@
*/ */
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.util.List;
import org.joda.time.LocalDate; import org.joda.time.LocalDate;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
public interface ScheduledDateGenerator { public interface ScheduledDateGenerator {
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays, LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO);
final WorkingDays workingDays);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
final LocalDate firstRepaymentDate); final LocalDate firstRepaymentDate);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment); LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO);
List<Holiday> holidays, WorkingDays workingDays);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate); LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date); Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays); final HolidayDetailDTO holidayDetailDTO);
} }
@@ -43,6 +43,7 @@ import org.mifosplatform.portfolio.group.domain.Group;
import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData;
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;
@@ -358,30 +359,34 @@ public class LoanScheduleAssembler {
final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
workingDays);
} }
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId, public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges, List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
final List<LoanRepaymentScheduleInstallment> previousSchedule, final List<LoanRepaymentScheduleInstallment> previousSchedule,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom) { final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom,
LocalDate recalculateDueDateChargesFrom) {
final RoundingMode roundingMode = RoundingMode.HALF_EVEN; final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode); final MathContext mc = new MathContext(8, roundingMode);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository
.findOneWithNotFoundDetection(loanApplicationTerms.getCurrency());
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue()); .getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne(); final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod()); final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
return loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, loanApplicationTerms, loanCharges, HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, previousSchedule, final Long waitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
recalculateFrom); int penaltyWaitPeriod = 0;
if (waitPeriod != null) {
penaltyWaitPeriod = waitPeriod.intValue();
}
return loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, loanCharges, detailDTO, transactions,
loanRepaymentScheduleTransactionProcessor, previousSchedule, recalculateFrom, recalculateDueDateChargesFrom,
penaltyWaitPeriod);
} }
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
@@ -400,8 +405,8 @@ public class LoanScheduleAssembler {
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance(); CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1)); LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1));
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) { if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled, HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
holidays, workingDays); nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, detailDTO);
} }
return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate, return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate,
@@ -176,7 +176,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(), LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor, modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor,
LocalDate.now()); LocalDate.now(), LocalDate.now());
LoanScheduleData scheduleDate = model.toData(); LoanScheduleData scheduleDate = model.toData();
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods(); Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
for (LoanSchedulePeriodData periodData : periodDatas) { for (LoanSchedulePeriodData periodData : periodDatas) {
@@ -6,40 +6,37 @@
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext; import java.math.MathContext;
import java.util.List;
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.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.FlatInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory { public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
@Override @Override
public LoanRescheduleModel reschedule(final MathContext mathContext, public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final HolidayDetailDTO holidayDetailDTO) {
final List<Holiday> holidays, final WorkingDays workingDays) {
LoanRescheduleModel loanRescheduleModel = null;
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
switch (interestMethod) { case DECLINING_BALANCE:
case DECLINING_BALANCE: loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule( applicationCurrency, holidayDetailDTO);
mathContext, loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); break;
break;
case FLAT:
case FLAT: loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, applicationCurrency, holidayDetailDTO);
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); break;
break;
case INVALID:
case INVALID: break;
break; }
}
return loanRescheduleModel;
return loanRescheduleModel; }
}
} }
@@ -6,16 +6,14 @@
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
import java.math.MathContext; import java.math.MathContext;
import java.util.List;
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.workingdays.domain.WorkingDays; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public interface LoanReschedulerFactory { public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest, public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final List<Holiday> holidays, final WorkingDays workingDays); final HolidayDetailDTO holidayDetailDTO);
} }
@@ -18,6 +18,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays; 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.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
@@ -76,8 +77,9 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
final MathContext mathContext = new MathContext(8, roundingMode); final MathContext mathContext = new MathContext(8, roundingMode);
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive( List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); loanRescheduleRequest, applicationCurrency, holidayDetailDTO);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods); oldPeriods);
return loanRescheduleModelWithOldPeriods; return loanRescheduleModelWithOldPeriods;
@@ -34,6 +34,7 @@ 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.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -279,8 +280,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest); .createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays); loanRescheduleRequest, applicationCurrency, holidayDetailDTO);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods(); final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
@@ -426,8 +426,14 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
} }
chargeAmount = accruableForInstallment; chargeAmount = accruableForInstallment;
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1; boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge && installmentChargeData.getAmountAccrued() == null) { if (canAddCharge
applicableCharges.put(loanCharge, chargeAmount); && (installmentChargeData.getAmountAccrued() == null || chargeAmount.compareTo(installmentChargeData
.getAmountAccrued()) != 0)) {
BigDecimal amountForAccrual = chargeAmount;
if (installmentChargeData.getAmountAccrued() != null) {
amountForAccrual = chargeAmount.subtract(installmentChargeData.getAmountAccrued());
}
applicableCharges.put(loanCharge, amountForAccrual);
BigDecimal amountAccrued = chargeAmount; BigDecimal amountAccrued = chargeAmount;
if (loanCharge.getAmountAccrued() != null) { if (loanCharge.getAmountAccrued() != null) {
amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued()); amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued());
@@ -444,8 +450,12 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized()); chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized());
} }
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1; boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge && loanCharge.getAmountAccrued() == null) { if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) {
applicableCharges.put(loanCharge, chargeAmount); BigDecimal amountForAccrual = chargeAmount;
if (loanCharge.getAmountAccrued() != null) {
amountForAccrual = chargeAmount.subtract(loanCharge.getAmountAccrued());
}
applicableCharges.put(loanCharge, amountForAccrual);
} }
} }
@@ -487,7 +497,7 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
loanTransactionDatas.add(loanTransactionData); loanTransactionDatas.add(loanTransactionData);
} }
} }
Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator(); Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator();
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) { for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
if (recognized.compareTo(BigDecimal.ZERO) != 1 && unrecognized.compareTo(BigDecimal.ZERO) != 1 && iterator.hasNext()) { if (recognized.compareTo(BigDecimal.ZERO) != 1 && unrecognized.compareTo(BigDecimal.ZERO) != 1 && iterator.hasNext()) {
@@ -86,9 +86,11 @@ 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.api.LoanApiConstants; import org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants;
import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand; import org.mifosplatform.portfolio.loanaccount.command.LoanUpdateCommand;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargeData;
import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData;
import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData; import org.mifosplatform.portfolio.loanaccount.data.LoanInstallmentChargeData;
import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.mifosplatform.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine;
import org.mifosplatform.portfolio.loanaccount.domain.Loan; import org.mifosplatform.portfolio.loanaccount.domain.Loan;
@@ -120,6 +122,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.ScheduledDate
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
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.LoanOverdueDTO;
import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData;
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;
@@ -299,20 +302,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod, lastTransactionDate);
regenerateScheduleOnDisbursement(command, loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
holidays, workingDays, recalculateSchedule);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(), this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
loan, null); loan, null);
} }
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
allowTransactionsOnNonWorkingDay, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -496,19 +506,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getLoanTransactions().add(disbursementTransaction); loan.getLoanTransactions().add(disbursementTransaction);
} }
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
regenerateScheduleOnDisbursement(command, loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays,
workingDays, recalculateSchedule); allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(
loan.fetchRepaymentScheduleInstallments(), loan, null); loan.fetchRepaymentScheduleInstallments(), loan, null);
} }
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday, changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
allowTransactionsOnNonWorkingDay, restCalendarInstance);
} }
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
@@ -582,8 +599,12 @@ 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 = loan.undoDisbursal(this.loanScheduleFactory, applicationCurrency, existingTransactionIds, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO);
final Map<String, Object> changes = loan
.undoDisbursal(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds);
if (!changes.isEmpty()) { if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -740,6 +761,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ApplicationCurrency applicationCurrency = null; ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null; LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -752,12 +777,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getDisbursementDate(), loan, calendarInstance); loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail, final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds, defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, isHolidayEnabled, allowTransactionsOnNonWorkingDay, scheduleGeneratorDTO);
this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) { if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
if (paymentDetail != null) { if (paymentDetail != null) {
@@ -856,6 +887,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null; List<Holiday> holidays = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
WorkingDays workingDays = null; WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -870,12 +904,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne(); workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
final ChangedTransactionDetail changedTransactionDetail = loan HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds, ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory, calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance); lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
this.loanTransactionRepository.save(waiveInterestTransaction); this.loanTransactionRepository.save(waiveInterestTransaction);
@@ -949,14 +986,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(), final ChangedTransactionDetail changedTransactionDetail = loan.closeAsWrittenOff(command, defaultLoanLifecycleStateMachine(),
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory, changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
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()) {
@@ -1017,14 +1061,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CalendarInstance restCalendarInstance = null; CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes, ChangedTransactionDetail changedTransactionDetail = loan.close(command, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
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);
@@ -1133,8 +1185,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, chargeDefinition.getName()); throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, chargeDefinition.getName());
} }
addCharge(loan, chargeDefinition, loanCharge); final List<Long> existingTransactionIds = new ArrayList<>(loan.findExistingTransactionIds());
final List<Long> existingReversedTransactionIds = new ArrayList<>(loan.findExistingReversedTransactionIds());
boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (isAppliedOnBackDate && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
runScheduleRecalculation(loan);
}
updateOriginalSchedule(loan);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && isAppliedOnBackDate
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
this.loanAccountDomainService.recalculateAccruals(loan);
}
return new CommandProcessingResultBuilder() // return new CommandProcessingResultBuilder() //
.withCommandId(command.commandId()) // .withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) // .withEntityId(loanCharge.getId()) //
@@ -1145,7 +1214,71 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.build(); .build();
} }
private void addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) { public void runScheduleRecalculation(Loan loan) {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
CalendarInstance restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate()
.toDate());
WorkingDays workingDays = this.workingDaysRepository.findOne();
final Long overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
LocalDate recalculateFrom = null;
LocalDate lastTransactionDate = null;
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod, lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = loan
.handleRegenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
saveLoanWithDataIntegrityViolationChecks(loan);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
// update loan with references to the newly created
// transactions
loan.getLoanTransactions().add(mapEntry.getValue());
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
}
}
}
}
public void updateOriginalSchedule(Loan loan) {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
final MonetaryCurrency currency = loan.getCurrency();
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
LocalDate calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate()
.toDate());
WorkingDays workingDays = this.workingDaysRepository.findOne();
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO);
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
}
}
private boolean addCharge(final Loan loan, final Charge chargeDefinition, final LoanCharge loanCharge) {
if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) { if (!loan.hasCurrencyCodeOf(chargeDefinition.getCurrencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency."; final String errorMessage = "Charge and Loan must have the same currency.";
@@ -1161,34 +1294,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
this.loanChargeRepository.save(loanCharge); this.loanChargeRepository.save(loanCharge);
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(); final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge);
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.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
/** /**
* we want to apply charge transactions only for those loans charges * we want to apply charge transactions only for those loans charges
@@ -1217,13 +1325,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue()); this.accountTransfersWritePlatformService.updateLoanTransaction(mapEntry.getKey(), mapEntry.getValue());
} }
} }
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
}
// we post Journal entries only for loans in active status
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
} }
boolean isAppliedOnBackDate = false;
if (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate())) {
isAppliedOnBackDate = true;
}
return isAppliedOnBackDate;
} }
@Transactional @Transactional
@@ -1311,6 +1419,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null; List<Holiday> holidays = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
WorkingDays workingDays = null; WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
LocalDate lastTransactionDate = null;
Long overdurPenaltyWaitPeriod = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -1325,8 +1436,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne(); workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
Money accruedCharge = Money.zero(loan.getCurrency()); Money accruedCharge = Money.zero(loan.getCurrency());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) { if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy( Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
@@ -1337,8 +1454,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes, final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, holidays, workingDays, isHolidayEnabled, existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge);
loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance, accruedCharge);
this.loanTransactionRepository.save(waiveTransaction); this.loanTransactionRepository.save(waiveTransaction);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2011,39 +2127,43 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!overdueLoanScheduledInstallments.isEmpty()) { if (!overdueLoanScheduledInstallments.isEmpty()) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
final Map<Long, Collection<OverdueLoanScheduleData>> overdueScheduleData = new HashMap<>();
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) { for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduledInstallments) {
if (overdueScheduleData.containsKey(overdueInstallment.getLoanId())) {
overdueScheduleData.get(overdueInstallment.getLoanId()).add(overdueInstallment);
} else {
Collection<OverdueLoanScheduleData> loanData = new ArrayList<>();
loanData.add(overdueInstallment);
overdueScheduleData.put(overdueInstallment.getLoanId(), loanData);
}
}
for (final Long loanId : overdueScheduleData.keySet()) {
try { try {
applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString());
final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper,
null, null, null, null, null, overdueInstallment.getLoanId(), null, null, null, null);
applyChargeToOverdueLoanInstallment(overdueInstallment.getLoanId(), overdueInstallment.getChargeId(),
overdueInstallment.getPeriodNumber(), command);
} catch (final PlatformApiDataValidationException e) { } catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors(); final List<ApiParameterError> errors = e.getErrors();
for (final ApiParameterError error : errors) { for (final ApiParameterError error : errors) {
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ " with message " + error.getDeveloperMessage()); + error.getDeveloperMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(" with message ").append(error.getDeveloperMessage()); .append(error.getDeveloperMessage());
} }
} catch (final AbstractPlatformDomainRuleException ex) { } catch (final AbstractPlatformDomainRuleException ex) {
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ " with message " + ex.getDefaultUserMessage()); + ex.getDefaultUserMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(" with message ").append(ex.getDefaultUserMessage()); .append(ex.getDefaultUserMessage());
} catch (Exception e) { } catch (Exception e) {
Throwable realCause = e; Throwable realCause = e;
if (e.getCause() != null) { if (e.getCause() != null) {
realCause = e.getCause(); realCause = e.getCause();
} }
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId() logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ " with message " + realCause.getMessage()); + realCause.getMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId()) sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(" with message ").append(realCause.getMessage()); .append(realCause.getMessage());
} }
} }
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); } if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
@@ -2051,9 +2171,42 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
@Transactional @Transactional
public void applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber, public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas) {
final JsonCommand command) {
Loan loan = null;
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
boolean runInterestRecalculation = false;
for (final OverdueLoanScheduleData overdueInstallment : overdueLoanScheduleDatas) {
final JsonElement parsedCommand = this.fromApiJsonHelper.parse(overdueInstallment.toString());
final JsonCommand command = JsonCommand.from(overdueInstallment.toString(), parsedCommand, this.fromApiJsonHelper, null, null,
null, null, null, loanId, null, null, null, null);
LoanOverdueDTO overdueDTO = applyChargeToOverdueLoanInstallment(loanId, overdueInstallment.getChargeId(),
overdueInstallment.getPeriodNumber(), command, loan, existingTransactionIds, existingReversedTransactionIds);
loan = overdueDTO.getLoan();
runInterestRecalculation = runInterestRecalculation || overdueDTO.isRunInterestRecalculation();
}
if (loan != null) {
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (runInterestRecalculation && loan.isFeeCompoundingEnabledForInterestRecalculation()) {
runScheduleRecalculation(loan);
}
updateOriginalSchedule(loan);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() && runInterestRecalculation
&& loan.isFeeCompoundingEnabledForInterestRecalculation()) {
this.loanAccountDomainService.recalculateAccruals(loan);
}
}
}
public LoanOverdueDTO applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber,
final JsonCommand command, Loan loan, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
boolean runInterestRecalculation = false;
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(loanChargeId); final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(loanChargeId);
Collection<Integer> frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(loanId, Collection<Integer> frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(loanId,
@@ -2087,24 +2240,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
scheduleDates.remove(frequency); scheduleDates.remove(frequency);
} }
Loan loan = null;
LoanRepaymentScheduleInstallment installment = null; LoanRepaymentScheduleInstallment installment = null;
if (!scheduleDates.isEmpty()) { if (!scheduleDates.isEmpty()) {
loan = this.loanAssembler.assembleFrom(loanId); if (loan == null) {
checkClientOrGroupActive(loan); loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
}
installment = loan.fetchRepaymentScheduleInstallment(periodNumber); installment = loan.fetchRepaymentScheduleInstallment(periodNumber);
} }
if (loan != null) {
for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) { for (Map.Entry<Integer, LocalDate> entry : scheduleDates.entrySet()) {
final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue()); final LoanCharge loanCharge = LoanCharge.createNewFromJson(loan, chargeDefinition, command, entry.getValue());
LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment, LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment,
entry.getKey()); entry.getKey());
loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge); loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge);
addCharge(loan, chargeDefinition, loanCharge); boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate;
}
} }
return new LoanOverdueDTO(loan, runInterestRecalculation);
} }
@Override @Override
@@ -2122,6 +2283,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null; List<Holiday> holidays = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
WorkingDays workingDays = null; WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -2136,11 +2301,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne(); workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds, ChangedTransactionDetail changedTransactionDetail = loan.undoWrittenOff(existingTransactionIds, existingReversedTransactionIds,
isHolidayEnabled, holidays, workingDays, this.loanScheduleFactory, applicationCurrency, scheduleGeneratorDTO);
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());
@@ -2182,13 +2352,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
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; CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(), restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command, final ChangedTransactionDetail changedTransactionDetail = loan.updateDisbursementDateForTranche(loanDisbursementDetails, command,
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency, existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO);
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan); saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -2198,8 +2377,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, createLoanScheduleArchive(loan, scheduleGeneratorDTO);
workingDays);
} }
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan); this.loanAccountDomainService.recalculateAccruals(loan);
@@ -2299,6 +2477,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null; List<Holiday> holidays = null;
boolean isHolidayEnabled = false; boolean isHolidayEnabled = false;
WorkingDays workingDays = null; WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
final List<Long> existingTransactionIds = new ArrayList<>(); final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>(); final List<Long> existingReversedTransactionIds = new ArrayList<>();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -2315,9 +2496,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne(); workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
} }
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory, HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance, ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
existingTransactionIds, existingReversedTransactionIds); existingTransactionIds, existingReversedTransactionIds);
saveLoanWithDataIntegrityViolationChecks(loan); saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2340,23 +2526,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
} }
} }
private void createLoanScheduleArchive(final Loan loan, final ApplicationCurrency applicationCurrency, private void createLoanScheduleArchive(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays, LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO);
final WorkingDays workingDays) {
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentScheduleFromModel(loanScheduleModel); List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentScheduleFromModel(loanScheduleModel);
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null); this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null);
} }
private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final boolean recalculateSchedule,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays, final boolean recalculateSchedule) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName); BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
loan.regenerateScheduleOnDisbursement(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount);
isHolidayEnabled, holidays, workingDays, recalculateSchedule, actualDisbursementDate, emiAmount);
} }
private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) { private List<LoanRepaymentScheduleInstallment> retrieveRepaymentScheduleFromModel(LoanScheduleModel model) {
@@ -0,0 +1,22 @@
package org.mifosplatform.portfolio.loanproduct.data;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
public class LoanOverdueDTO {
private final Loan loan;
private final boolean runInterestRecalculation;
public LoanOverdueDTO(final Loan loan, final boolean runInterestRecalculation) {
this.loan = loan;
this.runInterestRecalculation = runInterestRecalculation;
}
public boolean isRunInterestRecalculation() {
return this.runInterestRecalculation;
}
public Loan getLoan() {
return this.loan;
}
}
@@ -52,4 +52,9 @@ public enum InterestRecalculationCompoundingMethod {
public String getCode() { public String getCode() {
return this.code; return this.code;
} }
public boolean isFeeCompoundingEnabled() {
return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue())
|| this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
}
} }