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);
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.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
new JournalEntry(Float.valueOf("50.0"), JournalEntry.TransactionType.DEBIT));
*/
/*
* this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
* "20 September 2011", new JournalEntry(Float.valueOf("50.0"),
* 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";
try {
this.schedulerJobHelper.excuteJob(jobName);
@@ -2645,10 +2649,15 @@ public class ClientLoanIntegrationTest {
loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
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.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
new JournalEntry(Float.valueOf("29.7"), JournalEntry.TransactionType.DEBIT));*/
/*
* this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
* "20 September 2011", new JournalEntry(Float.valueOf("29.7"),
* 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";
try {
@@ -2850,10 +2859,15 @@ public class ClientLoanIntegrationTest {
loanCharges = this.loanTransactionHelper.getLoanCharges(loanID);
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.checkJournalEntryForExpenseAccount(expenseAccount, "20 September 2011",
new JournalEntry(Float.valueOf("31.51"), JournalEntry.TransactionType.DEBIT));*/
/*
* this.journalEntryHelper.checkJournalEntryForAssetAccount(assetAccount,
* "20 September 2011", new JournalEntry( Float.valueOf("31.51"),
* 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";
try {
@@ -3141,7 +3155,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE,
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,
LoanApplicationTestBuilder.MIFOS_STANDARD_STRATEGY, new ArrayList<HashMap>(0));
@@ -3231,7 +3245,7 @@ public class ClientLoanIntegrationTest {
final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST,
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,
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,
LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_INTEREST_AND_FEE,
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,
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
public void testLoanScheduleWithInterestRecalculation_WITH_PERIODIC_ACCOUNTING() {
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) {
Map<String, Object> values = new HashMap<>(3);
values.put("dueDate", getDateAsArray(todaysDate, addMonths));
@@ -3560,6 +3681,15 @@ public class ClientLoanIntegrationTest {
final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod,
final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval,
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 ---------------------------------------");
LoanProductTestBuilder builder = new LoanProductTestBuilder()
.withPrincipal("10000000.00")
@@ -3578,7 +3708,7 @@ public class ClientLoanIntegrationTest {
builder = builder.withAccountingRulePeriodicAccrual(accounts);
}
final String loanProductJSON = builder.build(null);
final String loanProductJSON = builder.build(chargeId);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
@@ -222,6 +222,18 @@ public class ChargesHelper {
System.out.println(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() {
final HashMap<String, Object> map = new HashMap<>();
@@ -74,7 +74,7 @@ public final class ProductToGLAccountMappingFromApiJsonDeserializer {
// accounting related data validation
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)) {
@@ -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.command.LoanChargeCommand;
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.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
@@ -466,10 +468,7 @@ public class Loan extends AbstractPersistable<Long> {
this.accountNumberRequiresAutoGeneration = accountNumberRequiresAutoGeneration;
}
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge, final List<Long> existingTransactionIds,
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) {
public ChangedTransactionDetail addLoanCharge(final LoanCharge loanCharge) {
ChangedTransactionDetail changedTransactionDetail = null;
validateLoanIsNotClosed(loanCharge);
@@ -526,22 +525,14 @@ public class Loan extends AbstractPersistable<Long> {
// store Id's of existing loan transactions and existing reversed loan
// transactions
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (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());
}
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
if (!loanCharge.isDueAtDisbursement()) {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
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()) {
mapEntry.getValue().updateLoan(this);
}
@@ -652,12 +643,18 @@ public class Loan extends AbstractPersistable<Long> {
&& !loanCharge.isDueForCollectionFromAndUpToAndIncluding(disbursementDate, lastRepaymentPeriodDueDate)) {
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,
getDisbursementDate(), getLastRepaymentPeriodDueDate(), loanCharge.name());
getDisbursementDate(), lastRepaymentPeriodDueDate, loanCharge.name());
}
}
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) {
@@ -674,7 +671,8 @@ public class Loan extends AbstractPersistable<Long> {
if (removed) {
charge.setActive(false);
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
@@ -693,7 +691,7 @@ public class Loan extends AbstractPersistable<Long> {
***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges());
getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
}
updateLoanSummaryDerivedFields();
@@ -751,11 +749,12 @@ public class Loan extends AbstractPersistable<Long> {
***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges());
getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
} else {
// reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
}
updateLoanSummaryDerivedFields();
@@ -838,10 +837,7 @@ public class Loan extends AbstractPersistable<Long> {
public LoanTransaction waiveLoanCharge(final LoanCharge loanCharge, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Integer loanInstallmentNumber, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isHolidayEnabled, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation,
final Money accruedCharge) {
final Integer loanInstallmentNumber, final ScheduleGeneratorDTO scheduleGeneratorDTO, final Money accruedCharge) {
validateLoanIsNotClosed(loanCharge);
@@ -887,9 +883,7 @@ public class Loan extends AbstractPersistable<Long> {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (loanCharge.getDueLocalDate() == null || LocalDate.now().isAfter(loanCharge.getDueLocalDate()))) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
// Waive of charges whose due date falls after latest 'repayment'
// transaction dont require entire loan schedule to be reprocessed.
@@ -906,11 +900,12 @@ public class Loan extends AbstractPersistable<Long> {
***/
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
loanRepaymentScheduleTransactionProcessor.handleTransaction(getDisbursementDate(), allNonContraTransactionsPostDisbursement,
getCurrency(), this.repaymentScheduleInstallments, charges());
getCurrency(), this.repaymentScheduleInstallments, charges(), getLastUserTransactionForChargeCalc());
} else {
// reprocess loan schedule based on charge been waived.
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
}
updateLoanSummaryDerivedFields();
@@ -1032,34 +1027,74 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanScheduleDependentDerivedFields();
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() {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return; }
List<LoanTransaction> transactions = this.getLoanTransactions();
for (final LoanTransaction transaction : transactions) {
if (transaction.isAccrual()) {
transaction.reverse();
}
private void applyAccurals() {
Collection<LoanTransaction> accruals = retreiveListOfAccrualTransactions();
if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
applyPeriodicAccruals(accruals);
} else if (isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct()) {
updateAccrualsForNonPeriodicAccruals(accruals);
}
}
public boolean isAccrualsPresent() {
boolean isAccrualsPresent = false;
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { return isAccrualsPresent; }
List<LoanTransaction> transactions = this.getLoanTransactions();
for (final LoanTransaction transaction : transactions) {
if (transaction.isAccrual() && transaction.isNotReversed()) {
isAccrualsPresent = true;
break;
private void applyPeriodicAccruals(final Collection<LoanTransaction> accruals) {
for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) {
Money interest = Money.zero(getCurrency());
Money fee = Money.zero(getCurrency());
Money penality = Money.zero(getCurrency());
for (LoanTransaction loanTransaction : accruals) {
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() {
@@ -1339,8 +1374,9 @@ public class Loan extends AbstractPersistable<Long> {
public void recalculateAllCharges() {
Set<LoanCharge> charges = this.charges();
int penaltyWaitPeriod = 0;
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) {
final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge);
private void updateOverdueScheduleInstallment(final LoanCharge 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 totalChargeAmt = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageBased()) {
@@ -1390,9 +1443,46 @@ public class Loan extends AbstractPersistable<Long> {
chargeAmt = chargeAmt.divide(BigDecimal.valueOf(repaymentScheduleDetail().getNumberOfRepayments()));
}
}
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
totalChargeAmt);
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
if (loanCharge.isActive()) {
loanCharge.update(chargeAmt, loanCharge.getDueLocalDate(), amount, repaymentScheduleDetail().getNumberOfRepayments(),
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) {
@@ -1588,8 +1678,9 @@ public class Loan extends AbstractPersistable<Long> {
}
// charges are optional
int penaltyWaitPeriod = 0;
for (final LoanCharge loanCharge : charges()) {
recalculateLoanCharge(loanCharge);
recalculateLoanCharge(loanCharge, penaltyWaitPeriod);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1815,11 +1906,8 @@ public class Loan extends AbstractPersistable<Long> {
return ids;
}
public ChangedTransactionDetail disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
final JsonCommand command, final ApplicationCurrency currency, final Map<String, Object> actualChanges,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnHoliday, final boolean allowTransactionsOnNonWorkingDay,
final CalendarInstance calendarInstanceForInterestRecalculation) {
public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED,
LoanStatus.fromInt(this.loanStatus));
@@ -1836,15 +1924,15 @@ public class Loan extends AbstractPersistable<Long> {
actualChanges.put("dateFormat", command.dateFormat());
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay);
validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays);
validateDisbursementDateIsOnNonWorkingDay(holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
validateDisbursementDateIsOnHoliday(holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()
&& (fetchRepaymentScheduleInstallment(1).getDueDate().isBefore(LocalDate.now()) || isDisbursementMissed())) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
@@ -1870,9 +1958,7 @@ public class Loan extends AbstractPersistable<Long> {
}
public void regenerateScheduleOnDisbursement(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final boolean recalculateSchedule,
public void regenerateScheduleOnDisbursement(final ScheduleGeneratorDTO scheduleGeneratorDTO, final boolean recalculateSchedule,
final LocalDate actualDisbursementDate, BigDecimal emiAmount) {
boolean isEmiAmountChanged = false;
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) {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
regenerateRepaymentSchedule(scheduleGeneratorDTO);
}
}
@@ -1958,7 +2043,8 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
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()) {
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
* details/state.
*/
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
private void regenerateRepaymentSchedule(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LoanScheduleModel loanSchedule = regenerateScheduleModel(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
final LoanScheduleModel loanSchedule = regenerateScheduleModel(scheduleGeneratorDTO);
updateLoanSchedule(loanSchedule);
Set<LoanCharge> charges = this.charges();
for (LoanCharge loanCharge : charges) {
recalculateLoanCharge(loanCharge);
recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod());
}
}
public LoanScheduleModel regenerateScheduleModel(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
public LoanScheduleModel regenerateScheduleModel(final ScheduleGeneratorDTO scheduleGeneratorDTO) {
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 MathContext mc = new MathContext(8, roundingMode);
@@ -2113,14 +2194,14 @@ public class Loan extends AbstractPersistable<Long> {
loanVariationTermsData.add(data);
}
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency,
loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
calculatedRepaymentsStartingFromDate, getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
final LoanApplicationTerms loanApplicationTerms = LoanApplicationTerms.assembleFrom(scheduleGeneratorDTO.getApplicationCurrency(),
loanTermFrequency, loanTermPeriodFrequencyType, getDisbursementDate(), getExpectedFirstRepaymentOnDate(),
scheduleGeneratorDTO.getCalculatedRepaymentsStartingFromDate(), getInArrearsTolerance(), this.loanRepaymentScheduleDetail,
this.loanProduct.isMultiDisburseLoan(), this.fixedEmiAmount, disbursementData, this.maxOutstandingLoanBalance,
loanVariationTermsData, getInterestChargedFromDate());
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, charges(),
isHolidayEnabled, holidays, workingDays);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, loanApplicationTerms, charges(),
scheduleGeneratorDTO.getHolidayDetailDTO());
return loanSchedule;
}
@@ -2220,10 +2301,8 @@ public class Loan extends AbstractPersistable<Long> {
return chargesPayment;
}
public Map<String, Object> undoDisbursal(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
public Map<String, Object> undoDisbursal(final ScheduleGeneratorDTO scheduleGeneratorDTO, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO);
@@ -2254,8 +2333,7 @@ public class Loan extends AbstractPersistable<Long> {
// clear off actual disbusrement date so schedule regeneration
// uses expected date.
regenerateRepaymentSchedule(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays);
regenerateRepaymentSchedule(scheduleGeneratorDTO);
if (isDisbueseAmtChanged) {
updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement());
}
@@ -2298,38 +2376,35 @@ public class Loan extends AbstractPersistable<Long> {
this.loanTermVariations.clear();
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges());
wrapper.reprocess(getCurrency(), getDisbursementDate(), this.repaymentScheduleInstallments, charges(),
getLastUserTransactionForChargeCalc());
updateLoanSummaryDerivedFields();
}
public ChangedTransactionDetail waiveInterest(final LoanTransaction waiveInterestTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
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) {
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_REPAYMENT_OR_WAIVER,
waiveInterestTransaction.getTransactionDate());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(waiveInterestTransaction,
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
loanLifecycleStateMachine, null, scheduleGeneratorDTO);
return changedTransactionDetail;
}
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays,
final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final boolean isHolidayEnabled,
boolean isRecoveryRepayment, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
final List<Long> existingReversedTransactionIds, boolean isRecoveryRepayment, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
LoanEvent event = null;
if (isRecoveryRepayment) {
@@ -2338,31 +2413,36 @@ public class Loan extends AbstractPersistable<Long> {
event = LoanEvent.LOAN_REPAYMENT_OR_WAIVER;
}
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateAccountStatus(event);
validateActivityNotBeforeClientOrGroupTransferDate(event, repaymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(event, repaymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
final ChangedTransactionDetail changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(repaymentTransaction,
loanLifecycleStateMachine, null, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
loanLifecycleStateMachine, null, scheduleGeneratorDTO);
return changedTransactionDetail;
}
public void makeChargePayment(final Long chargeId, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean allowTransactionsOnNonWorkingDay, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
final HolidayDetailDTO holidayDetailDTO, final LoanTransaction paymentTransaction, final Integer installmentNumber) {
validateAccountStatus(LoanEvent.LOAN_CHARGE_PAYMENT);
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CHARGE_PAYMENT, paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
validateActivityNotBeforeLastTransactionDateForInterestRecalculation(LoanEvent.LOAN_CHARGE_PAYMENT,
paymentTransaction.getTransactionDate());
validateRepaymentDateIsOnHoliday(paymentTransaction.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(paymentTransaction.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
if (paymentTransaction.getTransactionDate().isAfter(new LocalDate())) {
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,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
ChangedTransactionDetail changedTransactionDetail = null;
@@ -2516,12 +2594,12 @@ public class Loan extends AbstractPersistable<Long> {
}
if (reprocess) {
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
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()) {
mapEntry.getValue().updateLoan(this);
}
@@ -2607,6 +2685,18 @@ public class Loan extends AbstractPersistable<Long> {
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) {
if (isOverPaid()) {
@@ -2767,13 +2857,13 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction newTransactionDetail,
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isHolidayEnabled, final boolean allowTransactionsOnNonWorkingDay,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), allowTransactionsOnHoliday, holidays);
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), workingDays, allowTransactionsOnNonWorkingDay);
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());
ChangedTransactionDetail changedTransactionDetail = null;
@@ -2803,17 +2893,14 @@ public class Loan extends AbstractPersistable<Long> {
if (newTransactionDetail.isRepayment() || newTransactionDetail.isInterestWaiver()) {
changedTransactionDetail = handleRepaymentOrRecoveryOrWaiverTransaction(newTransactionDetail, loanLifecycleStateMachine,
transactionForAdjustment, loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled,
holidays, workingDays, calendarInstanceForInterestRecalculation);
transactionForAdjustment, scheduleGeneratorDTO);
}
return changedTransactionDetail;
}
public ChangedTransactionDetail undoWrittenOff(final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final CalendarInstance calendarInstanceForInterestRecalculation) {
final List<Long> existingReversedTransactionIds, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO);
existingTransactionIds.addAll(findExistingTransactionIds());
@@ -2825,13 +2912,11 @@ public class Loan extends AbstractPersistable<Long> {
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges());
charges(), getLastUserTransactionForChargeCalc());
updateLoanSummaryDerivedFields();
return changedTransactionDetail;
}
@@ -2893,15 +2978,12 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final AppUser currentUser, final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
calendarInstanceForInterestRecalculation);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor);
validateAccountStatus(LoanEvent.WRITE_OFF_OUTSTANDING);
@@ -2960,25 +3042,20 @@ public class Loan extends AbstractPersistable<Long> {
return changedTransactionDetail;
}
private ChangedTransactionDetail closeDisbursements(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final CalendarInstance calendarInstanceForInterestRecalculation) {
private ChangedTransactionDetail closeDisbursements(final ScheduleGeneratorDTO scheduleGeneratorDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
ChangedTransactionDetail changedTransactionDetail = null;
if (isDisbursementAllowed() && atleastOnceDisbursed()) {
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount());
removeDisbursementDetail();
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
regenerateRepaymentSchedule(scheduleGeneratorDTO);
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
}
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
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()) {
mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue());
@@ -3003,9 +3080,7 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail close(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency currency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.LOAN_CLOSED);
@@ -3032,9 +3107,8 @@ public class Loan extends AbstractPersistable<Long> {
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(loanScheduleFactory, currency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor,
calendarInstanceForInterestRecalculation);
ChangedTransactionDetail changedTransactionDetail = closeDisbursements(scheduleGeneratorDTO,
loanRepaymentScheduleTransactionProcessor);
LoanTransaction loanTransaction = null;
if (isOpen()) {
@@ -3596,7 +3670,8 @@ public class Loan extends AbstractPersistable<Long> {
final List<Map<String, Object>> newLoanTransactions = new ArrayList<>();
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));
} else if (!existingTransactionIds.contains(transaction.getId())) {
newLoanTransactions.add(transaction.toMapData(currencyData));
@@ -3621,10 +3696,13 @@ public class Loan extends AbstractPersistable<Long> {
if (receivableInterest.isLessThanZero()) {
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.";
throw new InvalidLoanStateTransitionException("waive", "cannot.be.after.accrual.date", errorMessage, tillDate);
}*/
/*
* 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."
* ; throw new InvalidLoanStateTransitionException("waive",
* "cannot.be.after.accrual.date", errorMessage, tillDate); }
*/
}
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() {
Set<LoanCharge> loanCharges = new HashSet<>();
if (this.charges != null) {
@@ -4081,9 +4210,7 @@ public class Loan extends AbstractPersistable<Long> {
public ChangedTransactionDetail updateDisbursementDateForTranche(final LoanDisbursementDetails disbursementDetails,
final JsonCommand command, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final Map<String, Object> actualChanges, final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency currency, final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final CalendarInstance calendarInstanceForInterestRecalculation) {
final Map<String, Object> actualChanges, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
validateAccountStatus(LoanEvent.LOAN_EDIT_MULTI_DISBURSE_DATE);
final LocalDate expectedDisbursementDate = command.localDateValueOfParameterNamed(LoanApiConstants.disbursementDateParameterName);
disbursementDetails.updateExpectedDisbursementDate(expectedDisbursementDate.toDate());
@@ -4095,12 +4222,9 @@ public class Loan extends AbstractPersistable<Long> {
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
LocalDate recalculateFrom = null;
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
} else {
regenerateRepaymentSchedule(loanScheduleFactory, currency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
regenerateRepaymentSchedule(scheduleGeneratorDTO);
}
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
@@ -4108,7 +4232,7 @@ public class Loan extends AbstractPersistable<Long> {
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges());
charges(), getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
this.loanTransactions.add(mapEntry.getValue());
@@ -4178,31 +4302,40 @@ public class Loan extends AbstractPersistable<Long> {
return maturityDate;
}
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, final List<Long> existingTransactionIds,
final List<Long> existingReversedTransactionIds) {
public ChangedTransactionDetail recalculateScheduleFromLastTransaction(final ScheduleGeneratorDTO generatorDTO,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds) {
existingTransactionIds.addAll(findExistingTransactionIds());
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
LocalDate recalculateFrom = null;
List<LoanTransaction> loanTransactions = this.retreiveListOfTransactionsPostDisbursementExcludeAccruals();
for (LoanTransaction loanTransaction : loanTransactions) {
if (recalculateFrom == null || loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
recalculateFrom = loanTransaction.getTransactionDate();
}
}
/*
* LocalDate recalculateFrom = null; List<LoanTransaction>
* loanTransactions =
* this.retreiveListOfTransactionsPostDisbursementExcludeAccruals(); for
* (LoanTransaction loanTransaction : loanTransactions) { if
* (recalculateFrom == null ||
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
* recalculateFrom = loanTransaction.getTransactionDate(); } }
* generatorDTO.setRecalculateFrom(recalculateFrom);
*/
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
regenerateRepaymentScheduleWithInterestRecalculation(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays,
calendarInstanceForInterestRecalculation, recalculateFrom);
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
}
return processTransactions();
}
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
return processTransactions();
}
private ChangedTransactionDetail processTransactions() {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement();
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction(
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments,
charges());
charges(), getLastUserTransactionForChargeCalc());
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
mapEntry.getValue().updateLoan(this);
}
@@ -4216,28 +4349,38 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields();
this.loanTransactions.removeAll(changedTransactionDetail.getNewTransactionMappings().values());
return changedTransactionDetail;
return changedTransactionDetail;
}
private void regenerateRepaymentScheduleWithInterestRecalculation(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
private void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
final LoanScheduleModel loanSchedule = getRecalculatedSchedule(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, calendarInstanceForInterestRecalculation,
recalculateFrom);
LocalDate lastTransactionDate = getLastUserTransactionDate();
generatorDTO.setLastTransactionDate(lastTransactionDate);
final LoanScheduleModel loanSchedule = getRecalculatedSchedule(generatorDTO);
if (loanSchedule == null) { return; }
updateLoanSchedule(loanSchedule);
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();
for (LoanCharge loanCharge : charges) {
if (!loanCharge.isDueAtDisbursement()) {
if (loanCharge.getDueLocalDate() == null || !lastRepaymentDate.isBefore(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge);
loanCharge.updateWaivedAmount(getCurrency());
updateOverdueScheduleInstallment(loanCharge);
if (loanCharge.getDueLocalDate() == null || (!lastRepaymentDate.isBefore(loanCharge.getDueLocalDate()))) {
if (!loanCharge.isWaived() && !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod());
loanCharge.updateWaivedAmount(getCurrency());
}
} else {
loanCharge.setActive(false);
}
@@ -4246,28 +4389,25 @@ public class Loan extends AbstractPersistable<Long> {
updateLoanSummaryDerivedFields();
}
private LoanScheduleModel getRecalculatedSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final CalendarInstance calendarInstanceForInterestRecalculation, LocalDate recalculateFrom) {
private LoanScheduleModel getRecalculatedSchedule(final ScheduleGeneratorDTO generatorDTO) {
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa) { return null; }
if (!this.repaymentScheduleDetail().isInterestRecalculationEnabled() || isNpa || !isOpen()) { return null; }
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 MathContext mc = new MathContext(8, roundingMode);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(applicationCurrency,
calculatedRepaymentsStartingFromDate, calendarInstanceForInterestRecalculation);
final LoanApplicationTerms loanApplicationTerms = constructLoanApplicationTerms(generatorDTO.getApplicationCurrency(),
generatorDTO.getCalculatedRepaymentsStartingFromDate(), generatorDTO.getCalendarInstanceForInterestRecalculation());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
.determineProcessor(this.transactionProcessingStrategy);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency,
loanApplicationTerms, charges(), isHolidayEnabled, holidays, workingDays,
retreiveListOfTransactionsPostDisbursementExcludeAccruals(), loanRepaymentScheduleTransactionProcessor,
this.repaymentScheduleInstallments, recalculateFrom);
final LoanScheduleModel loanSchedule = loanScheduleGenerator.rescheduleNextInstallments(mc, loanApplicationTerms, charges(),
generatorDTO.getHolidayDetailDTO(), retreiveListOfTransactionsPostDisbursementExcludeAccruals(),
loanRepaymentScheduleTransactionProcessor, this.repaymentScheduleInstallments, generatorDTO.getRecalculateFrom(),
generatorDTO.getLastTransactionDate(), generatorDTO.getPenaltyWaitPeriod());
return loanSchedule;
}
@@ -4576,4 +4716,12 @@ public class Loan extends AbstractPersistable<Long> {
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.CommandProcessingResultBuilder;
import org.mifosplatform.infrastructure.core.data.DataValidatorBuilder;
import org.mifosplatform.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.mifosplatform.infrastructure.core.exception.PlatformApiDataValidationException;
import org.mifosplatform.organisation.holiday.domain.Holiday;
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.group.domain.Group;
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.ScheduleGeneratorDTO;
import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.mifosplatform.portfolio.loanaccount.service.LoanAccrualWritePlatformService;
import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler;
@@ -149,6 +152,9 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate recalculateDueDateChargesFrom = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -161,12 +167,17 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
calendarInstance);
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,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, allowTransactionsOnHoliday,
holidays, workingDays, allowTransactionsOnNonWorkingDay, isHolidayEnabled, isRecoveryRepayment, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isRecoveryRepayment,
scheduleGeneratorDTO);
saveLoanTransactionWithDataIntegrityViolationChecks(newRepaymentTransaction);
@@ -259,17 +270,21 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
final LoanTransaction newPaymentTransaction = LoanTransaction.loanPayment(null, loan.getOffice(), paymentAmout, paymentDetail,
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()) {
loan.handlePayDisbursementTransaction(chargeId, newPaymentTransaction, existingTransactionIds, existingReversedTransactionIds);
} 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,
allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay, newPaymentTransaction,
installmentNumber);
holidayDetailDTO, newPaymentTransaction, installmentNumber);
}
saveLoanTransactionWithDataIntegrityViolationChecks(newPaymentTransaction);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -422,7 +437,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
@Override
public void recalculateAccruals(Loan loan) {
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<>();
List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments();
Long loanId = loan.getId();
@@ -436,9 +452,6 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CurrencyData currencyData = applicationCurrency.toData();
Set<LoanCharge> loanCharges = loan.charges();
BigDecimal accruedInterestIncome = null;
BigDecimal accruedFeeIncome = null;
BigDecimal accruedPenaltyIncome = null;
for (LoanRepaymentScheduleInstallment installment : installments) {
if (!accruedTill.isBefore(installment.getDueDate())
@@ -463,7 +476,8 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(),
installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment
.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());
loanScheduleAccrualDatas.add(accrualData);
@@ -471,7 +485,11 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
}
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")
public class LoanInterestRecalculationDetails extends AbstractPersistable<Long> {
@SuppressWarnings("unused")
@OneToOne
@JoinColumn(name = "loan_id", nullable = false)
private Loan loan;
@@ -24,19 +24,27 @@ public class LoanOverdueInstallmentCharge extends AbstractPersistable<Long> {
@ManyToOne(optional = false)
@JoinColumn(name = "loan_schedule_id", referencedColumnName = "id", nullable = false)
private LoanRepaymentScheduleInstallment installment;
@Column(name="frequency_number")
@Column(name = "frequency_number")
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.installment = installment;
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)
private BigDecimal interestAccrued;
@Column(name = "fee_charges_amount", scale = 6, precision = 19, nullable = true)
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)
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)
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)
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)
private BigDecimal totalPaidInAdvance;
@@ -127,10 +133,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
this.obligationsMet = false;
this.recalculatedInterestComponent = recalculatedInterestComponent;
}
public LoanRepaymentScheduleInstallment(final Loan loan) {
this.loan = loan;
this.installmentNumber = null;
this.loan = loan;
this.installmentNumber = null;
this.fromDate = null;
this.dueDate = null;
this.obligationsMet = false;
@@ -204,6 +210,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getInterestCharged(currency).minus(interestAccountedFor);
}
public Money getInterestAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.interestAccrued);
}
public Money getFeeChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.feeChargesCharged);
}
@@ -226,6 +236,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getFeeChargesCharged(currency).minus(feeChargesAccountedFor);
}
public Money getFeeAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.feeAccrued);
}
public Money getPenaltyChargesCharged(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyCharges);
}
@@ -248,6 +262,10 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
return getPenaltyChargesCharged(currency).minus(feeChargesAccountedFor);
}
public Money getPenaltyAccrued(final MonetaryCurrency currency) {
return Money.of(currency, this.penaltyAccrued);
}
public boolean isInterestDue(final MonetaryCurrency currency) {
return getInterestOutstanding(currency).isGreaterThanZero();
}
@@ -264,10 +282,9 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void updateLoan(final Loan loan) {
this.loan = loan;
}
public boolean isPartlyPaid() {
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null
|| this.principalCompleted != null);
return !this.obligationsMet && (this.interestPaid != null || this.feeChargesPaid != null || this.principalCompleted != null);
}
public boolean isObligationsMet() {
@@ -507,6 +524,12 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
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) {
if (!this.obligationsMet && getTotalOutstanding(currency).isZero()) {
this.obligationsMet = true;
@@ -569,59 +592,57 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
public void setRecalculatedInterestComponent(boolean 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 int compare(LoanRepaymentScheduleInstallment arg0,
LoanRepaymentScheduleInstallment arg1) {
return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
}
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 int compare(LoanRepaymentScheduleInstallment arg0, LoanRepaymentScheduleInstallment arg1) {
return arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
}
};
public BigDecimal getTotalPaidInAdvance() {
return this.totalPaidInAdvance;
return this.totalPaidInAdvance;
}
public BigDecimal getTotalPaidLate() {
return this.totalPaidLate;
return this.totalPaidLate;
}
public LocalDate getObligationsMetOnDate() {
LocalDate obligationsMetOnDate = null;
if(this.obligationsMetOnDate != null) {
obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate);
}
return obligationsMetOnDate;
LocalDate obligationsMetOnDate = null;
if (this.obligationsMetOnDate != null) {
obligationsMetOnDate = new LocalDate(this.obligationsMetOnDate);
}
return obligationsMetOnDate;
}
}
@@ -20,24 +20,29 @@ import org.mifosplatform.organisation.monetary.domain.Money;
public class LoanRepaymentScheduleProcessingWrapper {
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 totalPrincipal = Money.zero(currency);
for (final LoanRepaymentScheduleInstallment installment : repaymentPeriods) {
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
}
LocalDate startDate = disbursementDate;
for (final LoanRepaymentScheduleInstallment period : repaymentPeriods) {
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,
currency, !period.isRecalculatedInterestComponent());
final Money feeChargesWrittenOffForRepaymentPeriod = cumulativeFeeChargesWrittenOffWithin(startDate, period.getDueDate(),
loanCharges, currency, !period.isRecalculatedInterestComponent());
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(),
loanCharges, currency, !period.isRecalculatedInterestComponent());
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,
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);
@@ -80,14 +86,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
&& loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO;
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()) {
amount = amount.add(totalInterest.getAmount());
} else {
amount = amount.add(period.getLoan().getPrincpal().getAmount());
amount = amount.add(totalPrincipal.getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
cumulative = cumulative.plus(loanChargeAmt);
@@ -144,7 +151,8 @@ public class LoanRepaymentScheduleProcessingWrapper {
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
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);
@@ -171,14 +179,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
&& loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO;
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()) {
amount = amount.add(totalInterest.getAmount());
} else {
amount = amount.add(period.getLoan().getPrincpal().getAmount());
amount = amount.add(totalPrincipal.getAmount());
}
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
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 LoanTransaction waiver = new LoanTransaction(loan, office, LoanTransactionType.WAIVE_CHARGES, waived.getAmount(), waiveDate,
null);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived,unrecognizedCharge);
waiver.updateChargesComponents(feeChargesWaived, penaltyChargesWaived, unrecognizedCharge);
return waiver;
}
@@ -304,7 +304,7 @@ public final class LoanTransaction extends AbstractPersistable<Long> {
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();
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).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.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
}
public void adjustInterestComponent(final MonetaryCurrency currency) {
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,
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) {
@@ -48,7 +48,16 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
@Override
public ChangedTransactionDetail handleTransaction(final LocalDate disbursementDate,
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) {
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
// loan charges)
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(currency, disbursementDate, installments, charges);
if (reprocessCharges) {
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
wrapper.reprocess(currency, disbursementDate, installments, charges, recalculateChargesFrom);
}
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
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 boolean isFeeCharge) {
// to.
if(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()){
if (loanTransaction.isRepayment() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
loanTransaction.resetDerivedComponents();
}
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);
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,
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments);
@@ -31,13 +32,18 @@ public interface LoanRepaymentScheduleTransactionProcessor {
List<RecalculationDetail> handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
List<LoanRepaymentScheduleInstallment> installments, LoanRepaymentScheduleInstallment currentInstallment,
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();
void applyTransaction(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
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.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -18,14 +19,13 @@ import java.util.TreeMap;
import org.joda.time.Days;
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.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -49,22 +49,21 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();
@Override
public LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
final List<RecalculationDetail> diffAmt = null;
final LocalDate prepayDate = null;
return generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays, diffAmt,
prepayDate);
final LocalDate lastTransactionDate = loanApplicationTerms.getExpectedDisbursementDate();
return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt, prepayDate, lastTransactionDate);
}
private LoanScheduleModel generate(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, List<RecalculationDetail> diffAmt, LocalDate prepayDate) {
private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, List<RecalculationDetail> diffAmt,
final LocalDate prepayDate, final LocalDate lastTransactionDate) {
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
// 1. generate list of proposed schedule due dates
final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
// 2. determine the total charges due at time of disbursement
@@ -144,7 +143,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
isFirstRepayment);
isFirstRepayment = false;
scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
holidayDetailDTO);
scheduledDueDateAsPerFrequency = scheduledDueDate;
} else {
recalculatedInterestComponent = true;
@@ -153,7 +152,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
LocalDate actualRepayment = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
loanApplicationTerms, isFirstRepayment);
scheduledDueDateAsPerFrequency = this.scheduledDateGenerator.adjustRepaymentDate(actualRepayment, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
holidayDetailDTO);
}
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 feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed,
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent());
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(),
lastTransactionDate);
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
loanScheduleModelPeriod.periodDueDate(), loanCharges, currency, principalInterest, principalDisbursed,
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent());
totalCumulativeInterest, numberOfRepayments, !loanScheduleModelPeriod.isRecalculatedInterestComponent(),
lastTransactionDate);
totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
@@ -549,8 +550,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
@@ -696,8 +696,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// update the number of repayments
loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
loanApplicationTerms.updateLoanEndDate(loanEndDate);
if (newInterestRate != null) {
@@ -739,7 +738,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate, loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
holidayDetailDTO);
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,
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod,
final Money principalDisbursed, final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments,
boolean isInstallmentChargeApplicable) {
boolean isInstallmentChargeApplicable, final LocalDate lastTransactionDate) {
Money cumulative = Money.zero(monetaryCurrency);
@@ -963,7 +962,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
&& loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
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,
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
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);
@@ -1013,7 +1014,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
&& loanCharge.getChargeCalculation().isPercentageBased()) {
cumulative = cumulative.plus(loanCharge.chargeAmount());
} else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
&& loanCharge.getChargeCalculation().isPercentageBased()) {
&& loanCharge.getChargeCalculation().isPercentageBased()
&& !lastTransactionDate.isAfter(loanCharge.getDueLocalDate())) {
BigDecimal amount = BigDecimal.ZERO;
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
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 )
*/
@Override
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
public LoanScheduleModel rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom) {
final List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom, final LocalDate lastTransactionDate,
final int penaltyWaitPeriod) {
LoanScheduleModel loanScheduleModel = null;
List<LoanRepaymentScheduleInstallment> installments = previousSchedule;
@@ -1064,8 +1066,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
installments.removeAll(removeInstallments);
if (previousSchedule == null) {
loanScheduleModel = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays,
workingDays);
loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO);
installments = retrieveRepaymentSchedule(loanScheduleModel);
}
@@ -1082,10 +1083,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
int installmentNumber = findLastProcessedInstallmentNumber(installments, processTransactionsForInterestCompound);
List<LoanTransaction> processTransactions = processTransactions(transactions, processTransactionsForInterestCompound);
if (processTransactions.size() > 0) {
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule,
loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, false,
installmentNumber, processTransactions, preCloseTransaction);
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency,
recalculationDetails, retainRecalculationDetails, false, installmentNumber, processTransactions,
preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
}
recalculationDetails.retainAll(retainRecalculationDetails.values());
processTransactionsForInterestCompound = getNextRecalculateFromDate(transactions, processTransactionsForInterestCompound);
@@ -1097,10 +1098,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanTransaction> processTransactions = processTransactions(transactions, recalculateFrom);
if (loanRepaymentScheduleTransactionProcessor.isInterestFirstRepaymentScheduleTransactionProcessor()
&& processTransactions.size() > 0) {
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule,
loanScheduleModel, installments, currency, recalculationDetails, retainRecalculationDetails, true,
installmentNumber, processTransactions, preCloseTransaction);
loanScheduleModel = createInterestOnlyRecalculationDetails(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, currency,
recalculationDetails, retainRecalculationDetails, true, installmentNumber, processTransactions,
preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
if (loanScheduleModel != null) {
installments = retrieveRepaymentSchedule(loanScheduleModel);
}
@@ -1114,9 +1115,9 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
recalculationDetails.retainAll(retainRecalculationDetails.values());
recalculationDetails.addAll(earlypayments);
loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled,
holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments,
processTransactions, installmentNumber, recalculationDetails, preCloseTransaction);
loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments, processTransactions,
installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod, transactions);
if (loanScheduleModel != null) {
installments = retrieveRepaymentSchedule(loanScheduleModel);
}
@@ -1130,14 +1131,14 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
/**
* Method identifies interest only repayment periods
*/
private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays,
private LoanScheduleModel createInterestOnlyRecalculationDetails(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency,
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);
if (!(loanTransaction.isRepayment() || loanTransaction.isInterestWaiver())) {
processTransactions.add(loanTransaction);
@@ -1187,10 +1188,10 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
recalculationDetails.add(recalculationDetail);
if (recalculate) {
recalculationDetails.retainAll(retainRecalculationDetails.values());
loanScheduleModel = recalculateInstallment(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, loanRepaymentScheduleTransactionProcessor, previousSchedule,
loanScheduleModel, installments, processTransactions, installmentNumber, recalculationDetails,
preCloseTransaction);
loanScheduleModel = recalculateInstallment(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
loanRepaymentScheduleTransactionProcessor, previousSchedule, loanScheduleModel, installments,
processTransactions, installmentNumber, recalculationDetails, preCloseTransaction, lastTransactionDate,
penaltyWaitPeriod, actualTransactions);
}
}
@@ -1221,19 +1222,20 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* @param preCloseTransaction
* TODO
*/
private LoanScheduleModel recalculateInstallment(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays,
private LoanScheduleModel recalculateInstallment(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
final List<LoanRepaymentScheduleInstallment> previousSchedule, LoanScheduleModel loanScheduleModel,
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;
Integer lastInstallmentNumber = previousSchedule.size();
while (processInstallmentsFrom < lastInstallmentNumber) {
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, processTransactions, loanRepaymentScheduleTransactionProcessor,
processInstallmentsFrom, installments, recalculationDetails, skipRecalculation, preCloseTransaction);
RecalculatedSchedule recalculatedSchedule = recalculateInterest(mc, loanApplicationTerms, loanCharges, holidayDetailDTO,
processTransactions, loanRepaymentScheduleTransactionProcessor, processInstallmentsFrom, installments,
recalculationDetails, skipRecalculation, preCloseTransaction, lastTransactionDate, penaltyWaitPeriod,
actualTransactions);
processInstallmentsFrom = recalculatedSchedule.getInstallmentNumber();
if (recalculatedSchedule.getLoanScheduleModel() != null) {
loanScheduleModel = recalculatedSchedule.getLoanScheduleModel();
@@ -1250,12 +1252,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
* @param preCloseTransaction
* TODO
*/
private RecalculatedSchedule recalculateInterest(final MathContext mc, final ApplicationCurrency applicationCurrency,
final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays, final List<LoanTransaction> transactions,
private RecalculatedSchedule recalculateInterest(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final int installmentNumber,
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;
int processedInstallmentNumber = installmentNumber;
@@ -1282,15 +1284,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
List<LoanTransaction> transactionsForInstallment = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
LocalDate transactionsDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms,
isHolidayEnabled, holidays, workingDays);
holidayDetailDTO);
for (LoanTransaction loanTransaction : transactions) {
LocalDate loantransactionDate = loanTransaction.getTransactionDate();
if (!loantransactionDate.isAfter(transactionsDate)) {
transactionsForInstallment.add(loanTransaction);
recalculationDates.put(
loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
recalculationDates.put(loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO));
}
}
@@ -1321,15 +1321,13 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// this block is to identify late payment based on the rest
// calculation date
while (totalOutstanding.isGreaterThanZero() && startDate.isBefore(LocalDate.now())) {
LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, isHolidayEnabled, holidays,
workingDays);
LocalDate recalculateFrom = getNextRestScheduleDate(startDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
LocalDate recalcualteTill = getNextRestScheduleDate(recalculateFrom, loanApplicationTerms, holidayDetailDTO);
if (reduceStartDate) {
startDate = startDate.minusDays(1);
}
applyRest(transactions, startDate, recalculateFrom, installment, loanRepaymentScheduleTransactionProcessor, currency,
loanApplicationTerms, isHolidayEnabled, holidays, workingDays, installments, preCloseTransaction);
loanApplicationTerms, holidayDetailDTO, installments, preCloseTransaction);
totalOutstanding = installment.getTotalOutstanding(currency);
if (totalOutstanding.isGreaterThanZero()) {
Money latepaymentoutstanding = installment.getPrincipalOutstanding(currency);
@@ -1373,12 +1371,130 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
if (preCloseTransaction != null) {
prepayDate = preCloseTransaction.getTransactionDate();
}
model = generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays,
recalculationDetails, prepayDate);
model = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails, prepayDate, lastTransactionDate);
model = updateOverDueCharges(loanCharges, model, lastTransactionDate, currency, loanRepaymentScheduleTransactionProcessor,
loanApplicationTerms.getExpectedDisbursementDate(), penaltyWaitPeriod, actualTransactions);
}
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) {
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
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,
LoanRepaymentScheduleInstallment installment,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, MonetaryCurrency currency,
final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays, final List<LoanRepaymentScheduleInstallment> installments, LoanTransaction preCloseTransaction) {
final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
final List<LoanRepaymentScheduleInstallment> installments, LoanTransaction preCloseTransaction) {
List<LoanTransaction> transactions = new ArrayList<>();
Map<LocalDate, LocalDate> recalculationDates = new HashMap<>();
for (LoanTransaction transaction : loanTransactions) {
LocalDate loantransactionDate = transaction.getTransactionDate();
if (loantransactionDate.isAfter(from) && !transaction.getTransactionDate().isAfter(to)) {
transactions.add(transaction);
recalculationDates.put(
loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, isHolidayEnabled, holidays,
workingDays));
recalculationDates.put(loantransactionDate,
getNextRestScheduleDate(loantransactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO));
}
}
@@ -1424,11 +1538,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
}
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final HolidayDetailDTO holidayDetailDTO) {
LocalDate nextScheduleDate = null;
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(startDate,
loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
loanApplicationTerms, holidayDetailDTO);
} else {
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
@@ -5,24 +5,20 @@
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.util.List;
import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.joda.time.Months;
import org.joda.time.Weeks;
import org.joda.time.Years;
import org.mifosplatform.organisation.holiday.domain.Holiday;
import org.mifosplatform.organisation.holiday.service.HolidayUtil;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
public LocalDate getLastRepaymentDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
@@ -32,7 +28,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO);
return lastRepaymentDate;
}
@@ -52,22 +48,23 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
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;
/**
* Fix for https://mifosforge.jira.com/browse/MIFOSX-1357
*/
// recursively check for the next working day.
while (WorkingDaysUtil.isNonWorkingDay(workingDays, adjustedDate)) {
while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), adjustedDate)) {
final LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(),
loanApplicationTerms.getRepaymentEvery(), adjustedDate);
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate, workingDays);
adjustedDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(adjustedDate, nextDueRepaymentPeriodDate,
holidayDetailDTO.getWorkingDays());
}
if (isHolidayEnabled) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidays);
if (holidayDetailDTO.isHolidayEnabled()) {
adjustedDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(adjustedDate, holidayDetailDTO.getHolidays());
}
return adjustedDate;
}
@@ -161,7 +158,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
@Override
public LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate,
LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays, WorkingDays workingDays) {
LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO) {
LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate();
boolean isFirstRepayment = true;
@@ -169,7 +166,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment);
isFirstRepayment = false;
}
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO);
return generatedDate;
}
}
@@ -110,7 +110,7 @@ public final class LoanApplicationTerms {
private BigDecimal maxOutstandingBalance;
private List<LoanTermVariationsData> emiAmountVariations;
private Money totalInterestDue;
private final DaysInMonthType daysInMonthType;
@@ -505,7 +505,7 @@ public final class LoanApplicationTerms {
final int periodsElapsed = 0;
final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, this.principal, periodsElapsed);
double totalRepayment = paymentPerRepaymentPeriod * this.numberOfRepayments;
return Money.of(this.principal.getCurrency(), BigDecimal.valueOf(totalRepayment));
}
@@ -521,16 +521,16 @@ public final class LoanApplicationTerms {
case FLAT:
final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc);
totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode());
break;
case DECLINING_BALANCE:
break;
case INVALID:
break;
}
if(this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue;
if (this.totalInterestDue != null) {
totalInterestDue = this.totalInterestDue;
}
return totalInterestDue;
@@ -544,7 +544,7 @@ public final class LoanApplicationTerms {
final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear);
final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm();
return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc)
.multiply(loanTermFrequencyBigDecimal);
}
@@ -1078,59 +1078,62 @@ public final class LoanApplicationTerms {
return numberOfPeriods;
}
public RecalculationFrequencyType getRecalculationFrequencyType() {
return this.recalculationFrequencyType;
}
public void updateNumberOfRepayments(final Integer numberOfRepayments) {
this.numberOfRepayments = numberOfRepayments;
this.numberOfRepayments = numberOfRepayments;
}
public void updatePrincipalGrace(final Integer principalGrace) {
this.principalGrace = principalGrace;
this.principalGrace = principalGrace;
}
public void updateInterestPaymentGrace(final Integer interestPaymentGrace) {
this.interestPaymentGrace = interestPaymentGrace;
this.interestPaymentGrace = interestPaymentGrace;
}
public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) {
if(interestRatePerPeriod != null) {
this.interestRatePerPeriod = interestRatePerPeriod;
}
if (interestRatePerPeriod != null) {
this.interestRatePerPeriod = interestRatePerPeriod;
}
}
public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) {
if(annualNominalInterestRate != null) {
this.annualNominalInterestRate = annualNominalInterestRate;
}
if (annualNominalInterestRate != null) {
this.annualNominalInterestRate = annualNominalInterestRate;
}
}
public BigDecimal getInterestRatePerPeriod() {
return this.interestRatePerPeriod;
return this.interestRatePerPeriod;
}
public BigDecimal getAnnualNominalInterestRate() {
return this.annualNominalInterestRate;
return this.annualNominalInterestRate;
}
public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) {
if(interestChargedFromDate != null) {
this.interestChargedFromDate = interestChargedFromDate;
}
if (interestChargedFromDate != null) {
this.interestChargedFromDate = interestChargedFromDate;
}
}
public void updateLoanTermFrequency(Integer loanTermFrequency) {
if(loanTermFrequency != null) {
this.loanTermFrequency = loanTermFrequency;
}
if (loanTermFrequency != null) {
this.loanTermFrequency = loanTermFrequency;
}
}
public void updateTotalInterestDue(Money totalInterestDue) {
if(totalInterestDue != null) {
this.totalInterestDue = totalInterestDue;
}
if (totalInterestDue != null) {
this.totalInterestDue = totalInterestDue;
}
}
public ApplicationCurrency getApplicationCurrency() {
return this.currency;
}
}
@@ -10,10 +10,9 @@ import java.util.List;
import java.util.Set;
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.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.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
@@ -23,19 +22,19 @@ import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanResched
public interface LoanScheduleGenerator {
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays);
LoanScheduleModel generate(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
final HolidayDetailDTO holidayDetailDTO);
LoanScheduleModel rescheduleNextInstallments(MathContext mc, ApplicationCurrency applicationCurrency,
LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays,
WorkingDays workingDays, List<LoanTransaction> transactions,
LoanScheduleModel rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Set<LoanCharge> loanCharges,
final HolidayDetailDTO holidayDetailDTO, List<LoanTransaction> transactions,
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom);
List<LoanRepaymentScheduleInstallment> previousSchedule, LocalDate recalculateFrom, LocalDate lastTransactionDate,
int penaltyWaitPeriod);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments, 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,
final List<Holiday> holidays, final WorkingDays workingDays);
LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List<LoanRepaymentScheduleInstallment> installments,
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 HolidayDetailDTO holidayDetailDTO);
}
@@ -42,6 +42,15 @@ public final class LoanScheduleModel {
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,
final int loanTermInDays, final Money principalDisbursed, final BigDecimal totalPrincipalExpected,
final BigDecimal totalPrincipalPaid, final BigDecimal totalInterestCharged, final BigDecimal totalFeeChargesCharged,
@@ -85,4 +94,9 @@ public final class LoanScheduleModel {
public Collection<LoanScheduleModelPeriod> getPeriods() {
return this.periods;
}
public BigDecimal getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
}
@@ -5,30 +5,25 @@
*/
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;
import java.util.List;
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.loanaccount.data.HolidayDetailDTO;
public interface ScheduledDateGenerator {
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays,
final WorkingDays workingDays);
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
final LocalDate firstRepaymentDate);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled,
List<Holiday> holidays, WorkingDays workingDays);
LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
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.loanaccount.api.LoanApiConstants;
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.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -358,30 +359,34 @@ public class LoanScheduleAssembler {
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays,
workingDays);
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
return loanScheduleGenerator.generate(mc, loanApplicationTerms, loanCharges, detailDTO);
}
public LoanScheduleModel assembleForInterestRecalculation(final LoanApplicationTerms loanApplicationTerms, final Long officeId,
List<LoanTransaction> transactions, final Set<LoanCharge> loanCharges,
final List<LoanRepaymentScheduleInstallment> previousSchedule,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom) {
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, LocalDate recalculateFrom,
LocalDate recalculateDueDateChargesFrom) {
final RoundingMode roundingMode = RoundingMode.HALF_EVEN;
final MathContext mc = new MathContext(8, roundingMode);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository
.findOneWithNotFoundDetection(loanApplicationTerms.getCurrency());
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, loanApplicationTerms
.getExpectedDisbursementDate().toDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final LoanScheduleGenerator loanScheduleGenerator = this.loanScheduleFactory.create(loanApplicationTerms.getInterestMethod());
return loanScheduleGenerator.rescheduleNextInstallments(mc, applicationCurrency, loanApplicationTerms, loanCharges,
isHolidayEnabled, holidays, workingDays, transactions, loanRepaymentScheduleTransactionProcessor, previousSchedule,
recalculateFrom);
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
final Long waitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
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,
@@ -400,8 +405,8 @@ public class LoanScheduleAssembler {
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
LocalDate nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), onDate.minusDays(1));
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, isHolidayEnabled,
holidays, workingDays);
HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
nextScheduleDate = scheduledDateGenerator.adjustRepaymentDate(nextScheduleDate, loanApplicationTerms, detailDTO);
}
return loanScheduleGenerator.calculatePrepaymentAmount(installments, currency, nextScheduleDate,
@@ -176,7 +176,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
LoanScheduleModel model = this.loanScheduleAssembler.assembleForInterestRecalculation(loanApplicationTerms, loan.getOfficeId(),
modifiedTransactions, loan.charges(), loan.fetchRepaymentScheduleInstallments(), loanRepaymentScheduleTransactionProcessor,
LocalDate.now());
LocalDate.now(), LocalDate.now());
LoanScheduleData scheduleDate = model.toData();
Collection<LoanSchedulePeriodData> periodDatas = scheduleDate.getPeriods();
for (LoanSchedulePeriodData periodData : periodDatas) {
@@ -6,40 +6,37 @@
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
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.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.FlatInterestLoanScheduleGenerator;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext,
final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays) {
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(
mathContext, loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
break;
case INVALID:
break;
}
return loanRescheduleModel;
}
@Override
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO) {
LoanRescheduleModel loanRescheduleModel = null;
switch (interestMethod) {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO);
break;
case INVALID:
break;
}
return loanRescheduleModel;
}
}
@@ -6,16 +6,14 @@
package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain;
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.workingdays.domain.WorkingDays;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod;
public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final boolean isHolidayEnabled,
final List<Holiday> holidays, final WorkingDays workingDays);
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
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.workingdays.domain.WorkingDays;
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.loanschedule.domain.LoanRepaymentScheduleHistory;
import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
@@ -76,8 +77,9 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
final MathContext mathContext = new MathContext(8, roundingMode);
List<LoanRepaymentScheduleHistory> oldPeriods = this.loanScheduleHistoryWritePlatformService.createLoanScheduleArchive(
loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
loanRescheduleRequest, applicationCurrency, holidayDetailDTO);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods);
return loanRescheduleModelWithOldPeriods;
@@ -34,6 +34,7 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper;
@@ -279,8 +280,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = this.loanScheduleHistoryWritePlatformService
.createLoanScheduleArchive(loan.getRepaymentScheduleInstallments(), loan, loanRescheduleRequest);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, isHolidayEnabled, holidays, workingDays);
loanRescheduleRequest, applicationCurrency, holidayDetailDTO);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
@@ -426,8 +426,14 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
}
chargeAmount = accruableForInstallment;
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge && installmentChargeData.getAmountAccrued() == null) {
applicableCharges.put(loanCharge, chargeAmount);
if (canAddCharge
&& (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;
if (loanCharge.getAmountAccrued() != null) {
amountAccrued = amountAccrued.add(loanCharge.getAmountAccrued());
@@ -444,8 +450,12 @@ public class LoanAccrualWritePlatformServiceImpl implements LoanAccrualWritePlat
chargeAmount = chargeAmount.subtract(loanCharge.getAmountUnrecognized());
}
boolean canAddCharge = chargeAmount.compareTo(BigDecimal.ZERO) == 1;
if (canAddCharge && loanCharge.getAmountAccrued() == null) {
applicableCharges.put(loanCharge, chargeAmount);
if (canAddCharge && (loanCharge.getAmountAccrued() == null || chargeAmount.compareTo(loanCharge.getAmountAccrued()) != 0)) {
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);
}
}
Iterator<LoanTransactionData> iterator = loanTransactionDatas.iterator();
for (LoanSchedulePeriodData loanSchedulePeriodData : loanSchedulePeriodDatas) {
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.loanaccount.api.LoanApiConstants;
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.LoanChargePaidByData;
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.DefaultLoanLifecycleStateMachine;
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.serialization.LoanEventApiJsonValidator;
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.exception.InvalidCurrencyException;
import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException;
@@ -299,20 +302,27 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
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,
holidays, workingDays, recalculateSchedule);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
loan, null);
}
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay, restCalendarInstance);
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
}
if (!changes.isEmpty()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -496,19 +506,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getLoanTransactions().add(disbursementTransaction);
}
CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(
loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
}
regenerateScheduleOnDisbursement(command, loan, applicationCurrency, firstRepaymentOnDate, isHolidayEnabled, holidays,
workingDays, recalculateSchedule);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays,
allowTransactionsOnHoliday, allowTransactionsOnNonWorkingDay);
final ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
firstRepaymentOnDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(
loan.fetchRepaymentScheduleInstallments(), loan, null);
}
changedTransactionDetail = loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, changes,
firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays, allowTransactionsOnHoliday,
allowTransactionsOnNonWorkingDay, restCalendarInstance);
changedTransactionDetail = loan.disburse(currentUser, command, changes, scheduleGeneratorDTO);
}
if (!changes.isEmpty()) {
@@ -582,8 +599,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Map<String, Object> changes = loan.undoDisbursal(this.loanScheduleFactory, applicationCurrency, existingTransactionIds,
existingReversedTransactionIds, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(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()) {
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -740,6 +761,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
boolean isHolidayEnabled = false;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -752,12 +777,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getDisbursementDate(), loan, calendarInstance);
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,
defaultLoanLifecycleStateMachine(), transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
allowTransactionsOnHoliday, holidays, workingDays, isHolidayEnabled, allowTransactionsOnNonWorkingDay,
this.loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
scheduleGeneratorDTO);
if (newTransactionDetail.isGreaterThanZero(loan.getPrincpal().getCurrency())) {
if (paymentDetail != null) {
@@ -856,6 +887,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -870,12 +904,15 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
}
final ChangedTransactionDetail changedTransactionDetail = loan
.waiveInterest(waiveInterestTransaction, defaultLoanLifecycleStateMachine(), existingTransactionIds,
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance);
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
final ChangedTransactionDetail changedTransactionDetail = loan.waiveInterest(waiveInterestTransaction,
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
this.loanTransactionRepository.save(waiveInterestTransaction);
@@ -949,14 +986,21 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
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(),
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, this.loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
changes, existingTransactionIds, existingReversedTransactionIds, currentUser, scheduleGeneratorDTO);
LoanTransaction writeoff = changedTransactionDetail.getNewTransactionMappings().remove(0L);
this.loanTransactionRepository.save(writeoff);
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);
CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
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,
existingTransactionIds, existingReversedTransactionIds, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
existingTransactionIds, existingReversedTransactionIds, scheduleGeneratorDTO);
final LoanTransaction possibleClosingTransaction = changedTransactionDetail.getNewTransactionMappings().remove(0L);
if (possibleClosingTransaction != null) {
this.loanTransactionRepository.save(possibleClosingTransaction);
@@ -1133,8 +1185,25 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
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() //
.withCommandId(command.commandId()) //
.withEntityId(loanCharge.getId()) //
@@ -1145,7 +1214,71 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
.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())) {
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);
CalendarInstance restCalendarInstance = null;
ApplicationCurrency applicationCurrency = null;
LocalDate calculatedRepaymentsStartingFromDate = null;
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
final MonetaryCurrency currency = loan.getCurrency();
applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
calculatedRepaymentsStartingFromDate = this.loanAccountDomainService.getCalculatedRepaymentsStartingFromDate(
loan.getDisbursementDate(), loan, calendarInstance);
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
}
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge, existingTransactionIds,
existingReversedTransactionIds, holidays, workingDays, isHolidayEnabled, loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
final ChangedTransactionDetail changedTransactionDetail = loan.addLoanCharge(loanCharge);
/**
* 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());
}
}
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
@@ -1311,6 +1419,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
LocalDate lastTransactionDate = null;
Long overdurPenaltyWaitPeriod = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -1325,8 +1436,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
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());
if (loan.isPeriodicAccrualAccountingEnabledOnLoanProduct()) {
Collection<LoanChargePaidByData> chargePaidByDatas = this.loanChargeReadPlatformService.retriveLoanChargesPaidBy(
@@ -1337,8 +1454,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
final LoanTransaction waiveTransaction = loan.waiveLoanCharge(loanCharge, defaultLoanLifecycleStateMachine(), changes,
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, holidays, workingDays, isHolidayEnabled,
loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, restCalendarInstance, accruedCharge);
existingTransactionIds, existingReversedTransactionIds, loanInstallmentNumber, scheduleGeneratorDTO, accruedCharge);
this.loanTransactionRepository.save(waiveTransaction);
saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2011,39 +2127,43 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
if (!overdueLoanScheduledInstallments.isEmpty()) {
final StringBuilder sb = new StringBuilder();
final Map<Long, Collection<OverdueLoanScheduleData>> overdueScheduleData = new HashMap<>();
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 {
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);
applyOverdueChargesForLoan(loanId, overdueScheduleData.get(loanId));
} catch (final PlatformApiDataValidationException e) {
final List<ApiParameterError> errors = e.getErrors();
for (final ApiParameterError error : errors) {
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId()
+ " with message " + error.getDeveloperMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId())
.append(" with message ").append(error.getDeveloperMessage());
logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ error.getDeveloperMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(error.getDeveloperMessage());
}
} catch (final AbstractPlatformDomainRuleException ex) {
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId()
+ " with message " + ex.getDefaultUserMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId())
.append(" with message ").append(ex.getDefaultUserMessage());
logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ ex.getDefaultUserMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(ex.getDefaultUserMessage());
} catch (Exception e) {
Throwable realCause = e;
if (e.getCause() != null) {
realCause = e.getCause();
}
logger.error("Apply Charges due for overdue loans failed for account:" + overdueInstallment.getLoanId()
+ " with message " + realCause.getMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(overdueInstallment.getLoanId())
.append(" with message ").append(realCause.getMessage());
logger.error("Apply Charges due for overdue loans failed for account:" + loanId + " with message "
+ realCause.getMessage());
sb.append("Apply Charges due for overdue loans failed for account:").append(loanId).append(" with message ")
.append(realCause.getMessage());
}
}
if (sb.length() > 0) { throw new JobExecutionException(sb.toString()); }
@@ -2051,9 +2171,42 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
@Transactional
public void applyChargeToOverdueLoanInstallment(final Long loanId, final Long loanChargeId, final Integer periodNumber,
final JsonCommand command) {
public void applyOverdueChargesForLoan(final Long loanId, Collection<OverdueLoanScheduleData> overdueLoanScheduleDatas) {
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);
Collection<Integer> frequencyNumbers = loanChargeReadPlatformService.retrieveOverdueInstallmentChargeFrequencyNumber(loanId,
@@ -2087,24 +2240,32 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
scheduleDates.remove(frequency);
}
Loan loan = null;
LoanRepaymentScheduleInstallment installment = null;
if (!scheduleDates.isEmpty()) {
loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
if (loan == null) {
loan = this.loanAssembler.assembleFrom(loanId);
checkClientOrGroupActive(loan);
existingTransactionIds.addAll(loan.findExistingTransactionIds());
existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds());
}
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,
entry.getKey());
loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge);
LoanOverdueInstallmentCharge overdueInstallmentCharge = new LoanOverdueInstallmentCharge(loanCharge, installment,
entry.getKey());
loanCharge.updateOverdueInstallmentCharge(overdueInstallmentCharge);
addCharge(loan, chargeDefinition, loanCharge);
boolean isAppliedOnBackDate = addCharge(loan, chargeDefinition, loanCharge);
runInterestRecalculation = runInterestRecalculation || isAppliedOnBackDate;
}
}
return new LoanOverdueDTO(loan, runInterestRecalculation);
}
@Override
@@ -2122,6 +2283,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue());
@@ -2136,11 +2301,16 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
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,
isHolidayEnabled, holidays, workingDays, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, restCalendarInstance);
scheduleGeneratorDTO);
if (changedTransactionDetail != null) {
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
this.loanTransactionRepository.save(mapEntry.getValue());
@@ -2182,13 +2352,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final List<Long> existingReversedTransactionIds = new ArrayList<>();
final Map<String, Object> changes = new LinkedHashMap<>();
CalendarInstance restCalendarInstance = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
restCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.loanInterestRecalculationDetailId(),
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,
existingTransactionIds, existingReversedTransactionIds, changes, this.loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance);
existingTransactionIds, existingReversedTransactionIds, changes, scheduleGeneratorDTO);
saveAndFlushLoanWithDataIntegrityViolationChecks(loan);
@@ -2198,8 +2377,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
createLoanScheduleArchive(loan, applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays,
workingDays);
createLoanScheduleArchive(loan, scheduleGeneratorDTO);
}
postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
this.loanAccountDomainService.recalculateAccruals(loan);
@@ -2299,6 +2477,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
List<Holiday> holidays = null;
boolean isHolidayEnabled = false;
WorkingDays workingDays = null;
LocalDate recalculateFrom = null;
Long overdurPenaltyWaitPeriod = null;
LocalDate lastTransactionDate = null;
final List<Long> existingTransactionIds = new ArrayList<>();
final List<Long> existingReversedTransactionIds = new ArrayList<>();
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
@@ -2315,9 +2496,14 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
workingDays = this.workingDaysRepository.findOne();
overdurPenaltyWaitPeriod = this.configurationDomainService.retrievePenaltyWaitPeriod();
}
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(loanScheduleFactory,
applicationCurrency, calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays, restCalendarInstance,
HolidayDetailDTO holidayDetailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays);
ScheduleGeneratorDTO generatorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetailDTO, restCalendarInstance, recalculateFrom, overdurPenaltyWaitPeriod,
lastTransactionDate);
ChangedTransactionDetail changedTransactionDetail = loan.recalculateScheduleFromLastTransaction(generatorDTO,
existingTransactionIds, existingReversedTransactionIds);
saveLoanWithDataIntegrityViolationChecks(loan);
@@ -2340,23 +2526,18 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
}
}
private void createLoanScheduleArchive(final Loan loan, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final boolean isHolidayEnabled, final List<Holiday> holidays,
final WorkingDays workingDays) {
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, isHolidayEnabled, holidays, workingDays);
private void createLoanScheduleArchive(final Loan loan, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
LoanScheduleModel loanScheduleModel = loan.regenerateScheduleModel(scheduleGeneratorDTO);
List<LoanRepaymentScheduleInstallment> installments = retrieveRepaymentScheduleFromModel(loanScheduleModel);
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(installments, loan, null);
}
private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan,
final ApplicationCurrency applicationCurrency, final LocalDate calculatedRepaymentsStartingFromDate,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays, final boolean recalculateSchedule) {
private void regenerateScheduleOnDisbursement(final JsonCommand command, final Loan loan, final boolean recalculateSchedule,
final ScheduleGeneratorDTO scheduleGeneratorDTO) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
loan.regenerateScheduleOnDisbursement(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate,
isHolidayEnabled, holidays, workingDays, recalculateSchedule, actualDisbursementDate, emiAmount);
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount);
}
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() {
return this.code;
}
public boolean isFeeCompoundingEnabled() {
return this.getValue().equals(InterestRecalculationCompoundingMethod.FEE.getValue())
|| this.getValue().equals(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue());
}
}