Merge pull request #457 from ashok-conflux/MIFOSX-543
MIFOSX-543: Implement working days for Loans
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE `m_working_days` (
|
||||
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
`recurrence` VARCHAR(100) NULL DEFAULT NULL,
|
||||
`repayment_rescheduling_enum` SMALLINT(5) NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
)
|
||||
COLLATE='utf8_general_ci'
|
||||
ENGINE=InnoDB
|
||||
AUTO_INCREMENT=1;
|
||||
|
||||
INSERT INTO `m_working_days` (`recurrence`, `repayment_rescheduling_enum`) VALUES ('FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA', 2);
|
||||
+10
@@ -17,4 +17,14 @@ public class HolidayUtil {
|
||||
}
|
||||
return repaymentDate;
|
||||
}
|
||||
|
||||
public static boolean isHoliday(final LocalDate date, final List<Holiday> holidays){
|
||||
for (Holiday holiday : holidays) {
|
||||
if (date.isEqual(holiday.getFromDateLocalDate()) || date.isEqual(holiday.getToDateLocalDate()) || (date.isAfter(holiday.getFromDateLocalDate()) && date.isBefore(holiday.getToDateLocalDate()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -113,14 +113,14 @@ public class HolidayWritePlatformServiceJpaRepositoryImpl implements HolidayWrit
|
||||
throw new HolidayDateException("to.date.cannot.be.before.from.date", defaultUserMessage, fromDate.toString(), toDate.toString());
|
||||
}
|
||||
|
||||
//validate alternative working date
|
||||
//validate repaymentsRescheduledTo date
|
||||
//1. should be within a 7 days date range.
|
||||
//2. Alternative date should not be an exist holiday.//TBD
|
||||
//3. Holiday should not be on an alternate working date of another holiday.//TBD
|
||||
//3. Holiday should not be on an repaymentsRescheduledTo date of another holiday.//TBD
|
||||
|
||||
//restricting alternative working date to be within 7 days range before or after from date and to date.
|
||||
//restricting repaymentsRescheduledTo date to be within 7 days range before or after from date and to date.
|
||||
if(repaymentsRescheduledTo.isBefore(fromDate.minusDays(7)) || repaymentsRescheduledTo.isAfter(toDate.plusDays(7))){
|
||||
final String defaultUserMessage = "Alternative working date must be within 7 days before or after from and to dates";
|
||||
final String defaultUserMessage = "Repayments Rescheduled to date must be within 7 days before or after from and to dates";
|
||||
throw new HolidayDateException("repayments.rescheduled.to.must.be.within.range", defaultUserMessage, fromDate.toString(), toDate.toString(), repaymentsRescheduledTo.toString());
|
||||
}
|
||||
}
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.organisation.workingdays.domain;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public enum RepaymentRescheduleType {
|
||||
|
||||
INVALID(0, "RepaymentRescheduleType.invalid"),
|
||||
SAME_DAY(1, "RepaymentRescheduleType.same.day"),
|
||||
MOVE_TO_NEXT_WORKING_DAY(2,"RepaymentRescheduleType.move.to.next.working.day"),
|
||||
MOVE_TO_NEXT_REPAYMENT_MEETING_DAY(3,"RepaymentRescheduleType.move.to.next.repayment.meeting.day"),
|
||||
MOVE_TO_PREVIOUS_WORKING_DAY(4,"RepaymentRescheduleType.move.to.previous.working.day");
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
private RepaymentRescheduleType(final Integer value, final String code) {
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Integer getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
private static final Map<Integer, RepaymentRescheduleType> intToEnumMap = new HashMap<Integer, RepaymentRescheduleType>();
|
||||
private static int minValue;
|
||||
private static int maxValue;
|
||||
static {
|
||||
int i = 0;
|
||||
for (final RepaymentRescheduleType entityType : RepaymentRescheduleType.values()) {
|
||||
if (i == 0) {
|
||||
minValue = entityType.value;
|
||||
}
|
||||
intToEnumMap.put(entityType.value, entityType);
|
||||
if (minValue >= entityType.value) {
|
||||
minValue = entityType.value;
|
||||
}
|
||||
if (maxValue < entityType.value) {
|
||||
maxValue = entityType.value;
|
||||
}
|
||||
i = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static RepaymentRescheduleType fromInt(final int i) {
|
||||
final RepaymentRescheduleType RepaymentRescheduleType = intToEnumMap.get(Integer.valueOf(i));
|
||||
return RepaymentRescheduleType;
|
||||
}
|
||||
|
||||
public static int getMinValue() {
|
||||
return minValue;
|
||||
}
|
||||
|
||||
public static int getMaxValue() {
|
||||
return maxValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toString();
|
||||
}
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package org.mifosplatform.organisation.workingdays.domain;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.springframework.data.jpa.domain.AbstractPersistable;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_working_days")
|
||||
public class WorkingDays extends AbstractPersistable<Long> {
|
||||
|
||||
@Column(name = "recurrence", length = 100, nullable = true)
|
||||
private String recurrence;
|
||||
|
||||
@Column(name = "repayment_rescheduling_enum", nullable = false)
|
||||
private Integer repaymentReschedulingType;
|
||||
|
||||
protected WorkingDays() {
|
||||
|
||||
}
|
||||
|
||||
protected WorkingDays(String recurrence, Integer repaymentReschedulingType) {
|
||||
this.recurrence = recurrence;
|
||||
this.repaymentReschedulingType = repaymentReschedulingType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the recurrence
|
||||
*/
|
||||
public String getRecurrence() {
|
||||
return this.recurrence;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return the repaymentReschedulingType
|
||||
*/
|
||||
public Integer getRepaymentReschedulingType() {
|
||||
return this.repaymentReschedulingType;
|
||||
}
|
||||
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.organisation.workingdays.domain;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface WorkingDaysRepository extends JpaRepository<WorkingDays, Long>, JpaSpecificationExecutor<WorkingDays> {
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.organisation.workingdays.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.mifosplatform.organisation.workingdays.exception.WorkingDaysNotFoundException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Wrapper for {@link WorkingDaysRepository} that adds NULL checking and Error
|
||||
* handling capabilities
|
||||
* </p>
|
||||
*/
|
||||
@Service
|
||||
public class WorkingDaysRepositoryWrapper {
|
||||
|
||||
private final WorkingDaysRepository repository;
|
||||
|
||||
@Autowired
|
||||
public WorkingDaysRepositoryWrapper(final WorkingDaysRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public WorkingDays findOne() {
|
||||
final List<WorkingDays> workingDaysList = this.repository.findAll();
|
||||
|
||||
if (workingDaysList == null || workingDaysList.isEmpty()) {
|
||||
throw new WorkingDaysNotFoundException();
|
||||
}
|
||||
return workingDaysList.get(0);
|
||||
}
|
||||
|
||||
public void save(final WorkingDays workingDays) {
|
||||
this.repository.save(workingDays);
|
||||
}
|
||||
|
||||
public void saveAndFlush(final WorkingDays workingDays) {
|
||||
this.repository.saveAndFlush(workingDays);
|
||||
}
|
||||
|
||||
public void delete(final WorkingDays workingDays) {
|
||||
this.repository.delete(workingDays);
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
package org.mifosplatform.organisation.workingdays.exception;
|
||||
|
||||
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
|
||||
|
||||
/**
|
||||
* A {@link RuntimeException} thrown when Calendar resources are not found.
|
||||
*/
|
||||
public class WorkingDaysNotFoundException extends AbstractPlatformResourceNotFoundException {
|
||||
|
||||
public WorkingDaysNotFoundException() {
|
||||
super("error.msg.working.days.not.configured", "Must configure the Working days for the organisation.");
|
||||
}
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package org.mifosplatform.organisation.workingdays.service;
|
||||
|
||||
import org.joda.time.LocalDate;
|
||||
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||
|
||||
public class WorkingDaysUtil {
|
||||
|
||||
public static LocalDate getOffSetDateIfNonWorkingDay(final LocalDate date, final LocalDate nextMeetingDate, final WorkingDays workingDays) {
|
||||
|
||||
//If date is not a non working day then return date.
|
||||
if (isWorkingDay(workingDays, date)) { return date; }
|
||||
|
||||
final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(workingDays
|
||||
.getRepaymentReschedulingType());
|
||||
|
||||
switch (rescheduleType) {
|
||||
case INVALID:
|
||||
return date;
|
||||
case SAME_DAY:
|
||||
return date;
|
||||
case MOVE_TO_NEXT_WORKING_DAY:
|
||||
return getOffSetDateIfNonWorkingDay(date.plusDays(1), nextMeetingDate, workingDays);
|
||||
case MOVE_TO_NEXT_REPAYMENT_MEETING_DAY:
|
||||
return nextMeetingDate;
|
||||
case MOVE_TO_PREVIOUS_WORKING_DAY:
|
||||
return getOffSetDateIfNonWorkingDay(date.minusDays(1), nextMeetingDate, workingDays);
|
||||
default:
|
||||
return date;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isWorkingDay(WorkingDays workingDays, final LocalDate date){
|
||||
return CalendarHelper.isValidRedurringDate(workingDays.getRecurrence(), date, date);
|
||||
}
|
||||
}
|
||||
+25
-14
@@ -27,6 +27,8 @@ import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.mifosplatform.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.mifosplatform.infrastructure.core.service.DateUtils;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
@@ -171,7 +173,7 @@ public class CalendarHelper {
|
||||
for (@SuppressWarnings("rawtypes")
|
||||
final Iterator iterator = weekDayList.iterator(); iterator.hasNext();) {
|
||||
final WeekDay weekDay = (WeekDay) iterator.next();
|
||||
humanReadable += DayNameEnum.from(weekDay.getDay()).getValue();
|
||||
humanReadable += DayNameEnum.from(weekDay.getDay()).getCode();
|
||||
}
|
||||
|
||||
} else if (recur.getFrequency().equals(Recur.MONTHLY)) {
|
||||
@@ -211,30 +213,35 @@ public class CalendarHelper {
|
||||
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||
if (recur == null) { return false; }
|
||||
|
||||
return isValidRedurringDate(recur, seedDate, date);
|
||||
return isValidRecurringDate(recur, seedDate, date);
|
||||
}
|
||||
|
||||
public static boolean isValidRedurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date){
|
||||
public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date){
|
||||
|
||||
final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, date, date.plusDays(1), 1);
|
||||
return (recurDate == null || recurDate.isEmpty()) ? false : true;
|
||||
}
|
||||
|
||||
public static enum DayNameEnum{
|
||||
MO("Monday"),TU("Tuesday"),WE("Wednesday"),TH("Thursday"),FR("Friday"),SA("Saturday"),SU("Sunday");
|
||||
private final String value;
|
||||
|
||||
private DayNameEnum(String value){
|
||||
MO(1, "Monday"), TU(2, "Tuesday"), WE(3, "Wednesday"), TH(4, "Thursday"), FR(5, "Friday"), SA(6, "Saturday"), SU(7, "Sunday");
|
||||
private final String code;
|
||||
private final Integer value;
|
||||
private DayNameEnum(final Integer value, final String code){
|
||||
this.value = value;
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getValue(){
|
||||
public String getCode(){
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public static DayNameEnum from(String code){
|
||||
|
||||
public static DayNameEnum from(String name){
|
||||
for (DayNameEnum dayName : DayNameEnum.values()){
|
||||
if(dayName.toString().equals(code)) return dayName;
|
||||
if(dayName.toString().equals(name)) return dayName;
|
||||
}
|
||||
return DayNameEnum.MO;//Default it to Monday
|
||||
}
|
||||
@@ -302,11 +309,11 @@ public class CalendarHelper {
|
||||
return firstRepaymentDate;
|
||||
}
|
||||
|
||||
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency){
|
||||
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays){
|
||||
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||
if (recur == null) { return null; }
|
||||
|
||||
if(isValidRedurringDate(recur, seedDate, oldRepaymentDate)){
|
||||
if(isValidRecurringDate(recur, seedDate, oldRepaymentDate)){
|
||||
return oldRepaymentDate;
|
||||
}
|
||||
/* Recurring dates should follow loanRepaymentInterval.
|
||||
@@ -323,7 +330,11 @@ public class CalendarHelper {
|
||||
recur.setFrequency(frequency);
|
||||
}
|
||||
|
||||
final LocalDate newRepaymentDate = getNextRecurringDate(recur, seedDate, oldRepaymentDate);
|
||||
LocalDate newRepaymentDate = getNextRecurringDate(recur, seedDate, oldRepaymentDate);
|
||||
LocalDate nextRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
|
||||
|
||||
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
|
||||
|
||||
return newRepaymentDate;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -83,7 +83,7 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
|
||||
|
||||
if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
|
||||
// update all loans which are following this meeting calendar
|
||||
this.loanWritePlatformService.updateRepaymentsSchedule(calendarForUpdate, loanCalendarInstances);
|
||||
this.loanWritePlatformService.applyMeetingDateChanges(calendarForUpdate, loanCalendarInstances);
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
+65
-18
@@ -53,6 +53,8 @@ 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.staff.domain.Staff;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
||||
import org.mifosplatform.portfolio.client.domain.Client;
|
||||
@@ -64,6 +66,7 @@ import org.mifosplatform.portfolio.loanaccount.command.LoanChargeCommand;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException;
|
||||
@@ -1072,7 +1075,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public void loanApplicationSubmittal(final AppUser currentUser, final LoanScheduleModel loanSchedule,
|
||||
final LoanApplicationTerms loanApplicationTerms, final LoanLifecycleStateMachine lifecycleStateMachine,
|
||||
final LocalDate submittedOn, final String externalId) {
|
||||
final LocalDate submittedOn, final String externalId, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays) {
|
||||
|
||||
updateLoanSchedule(loanSchedule);
|
||||
|
||||
@@ -1119,8 +1123,12 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
for (LoanCharge loanCharge : setOfLoanCharges()) {
|
||||
validateChargeHasValidSpecifiedDateIfApplicable(loanCharge, getDisbursementDate(), getLastRepaymentPeriodDueDate());
|
||||
}
|
||||
|
||||
//validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(workingDays);
|
||||
validateDisbursementDateIsOnHoliday(isHolidayEnabled, holidays);
|
||||
}
|
||||
|
||||
|
||||
private LocalDate determineExpectedMaturityDate() {
|
||||
int numberOfInstallments = this.repaymentScheduleInstallments.size();
|
||||
return this.repaymentScheduleInstallments.get(numberOfInstallments - 1).getDueDate();
|
||||
@@ -1309,7 +1317,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
public Map<String, Object> disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser,
|
||||
final JsonCommand command, final ApplicationCurrency currency, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds, final Map<String, Object> actualChanges, final PaymentDetail paymentDetail,
|
||||
final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
updateLoanToPreDisbursalState();
|
||||
|
||||
@@ -1332,10 +1340,14 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
actualChanges.put("dateFormat", command.dateFormat());
|
||||
actualChanges.put("actualDisbursementDate", command.stringValueOfParameterNamed("actualDisbursementDate"));
|
||||
|
||||
//validate if disbursement date is a holiday or a non-working day
|
||||
validateDisbursementDateIsOnNonWorkingDay(workingDays);
|
||||
validateDisbursementDateIsOnHoliday(isHolidayEnabled, holidays);
|
||||
|
||||
handleDisbursementTransaction(paymentDetail, actualDisbursementDate);
|
||||
|
||||
if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate)) {
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate, isHolidayEnabled, holidays);
|
||||
regenerateRepaymentSchedule(loanScheduleFactory, currency, firstRepaymentMeetingDate, isHolidayEnabled, holidays, workingDays);
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
} else {
|
||||
updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate);
|
||||
@@ -1363,7 +1375,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
*/
|
||||
private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory,
|
||||
final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays) {
|
||||
final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
final InterestMethod interestMethod = this.loanRepaymentScheduleDetail.getInterestMethod();
|
||||
final LoanScheduleGenerator loanScheduleGenerator = loanScheduleFactory.create(interestMethod);
|
||||
@@ -1382,8 +1394,7 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
loanTermPeriodFrequencyType, getDisbursementDate(), firstRepaymentOnDate, getInArrearsTolerance(),
|
||||
this.loanRepaymentScheduleDetail);
|
||||
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges,
|
||||
isHolidayEnabled, holidays);
|
||||
LoanScheduleModel loanSchedule = loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, this.charges, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
updateLoanSchedule(loanSchedule);
|
||||
}
|
||||
@@ -1508,8 +1519,12 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction,
|
||||
final LoanLifecycleStateMachine loanLifecycleStateMachine, final List<Long> existingTransactionIds,
|
||||
final List<Long> existingReversedTransactionIds) {
|
||||
final List<Long> existingReversedTransactionIds, final boolean isHolidayEnabled, final List<Holiday> holidays,
|
||||
final WorkingDays workingDays) {
|
||||
|
||||
validateRepaymentDateIsOnHoliday(repaymentTransaction.getTransactionDate(), isHolidayEnabled, holidays);
|
||||
validateRepaymentDateIsOnNonWorkingDay(repaymentTransaction.getTransactionDate(), workingDays);
|
||||
|
||||
existingTransactionIds.addAll(findExistingTransactionIds());
|
||||
existingReversedTransactionIds.addAll(findExistingReversedTransactionIds());
|
||||
|
||||
@@ -2346,12 +2361,9 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
|
||||
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
// update repayment dates of repayment schedule installaments
|
||||
LocalDate tmpFromDate = this.getDisbursementDate();// first repayment's
|
||||
// from date is same
|
||||
// as disbursement
|
||||
// date.
|
||||
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
//update repayment dates of repayment schedule installaments
|
||||
LocalDate tmpFromDate = this.getDisbursementDate();//first repayment's from date is same as disbursement date.
|
||||
LocalDate newRepaymentDate = null;
|
||||
for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) {
|
||||
final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate();
|
||||
@@ -2360,12 +2372,13 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail.getRepaymentPeriodFrequencyType();
|
||||
final String frequency = CalendarHelper.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
|
||||
final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery();
|
||||
|
||||
// reset repayment date with new meeting date
|
||||
newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate,
|
||||
loanRepaymentInterval, frequency);
|
||||
if (isHolidayEnabled) {
|
||||
newRepaymentDate = CalendarHelper.getNewRepaymentMeetingDate(recuringRule, meetingStartDate, oldDueDate, loanRepaymentInterval, frequency, workingDays);
|
||||
if(isHolidayEnabled){
|
||||
newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays);
|
||||
}
|
||||
|
||||
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||
// reset from date to get actual daysInPeriod
|
||||
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
|
||||
@@ -2393,7 +2406,8 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
|
||||
if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.equals(holiday.getToDateLocalDate())
|
||||
|| (oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate()))) {
|
||||
// should be take from holiday
|
||||
//FIXME: AA do we need to apply non-working days.
|
||||
//Assuming holiday's repayment reschedule to date cannot be created on a non-working day.
|
||||
final LocalDate newRepaymentDate = holiday.getRepaymentsRescheduledToLocalDate();
|
||||
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||
}
|
||||
@@ -2401,6 +2415,39 @@ public class Loan extends AbstractPersistable<Long> {
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDisbursementDateIsOnNonWorkingDay(final WorkingDays workingDays) {
|
||||
if(!WorkingDaysUtil.isWorkingDay(workingDays, getDisbursementDate())){
|
||||
final String errorMessage = "Expected disbursement date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDisbursementDateIsOnHoliday(final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
if (isHolidayEnabled) {
|
||||
if (HolidayUtil.isHoliday(getDisbursementDate(), holidays)) {
|
||||
final String errorMessage = "Expected disbursement date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage,
|
||||
getExpectedDisbursedOnLocalDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRepaymentDateIsOnNonWorkingDay(final LocalDate repaymentDate, final WorkingDays workingDays) {
|
||||
if(!WorkingDaysUtil.isWorkingDay(workingDays, repaymentDate)){
|
||||
final String errorMessage = "Repayment date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("repayment.date.on.non.working.day", errorMessage, repaymentDate);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateRepaymentDateIsOnHoliday(final LocalDate repaymentDate, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
if (isHolidayEnabled) {
|
||||
if (HolidayUtil.isHoliday(repaymentDate, holidays)) {
|
||||
final String errorMessage = "Repayment date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("repayment.date.on.holiday", errorMessage, repaymentDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Group group() {
|
||||
return this.group;
|
||||
}
|
||||
|
||||
+4
-2
@@ -18,6 +18,7 @@ import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
|
||||
|
||||
/**
|
||||
@@ -30,10 +31,11 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
|
||||
@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 LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
// 1. generate list of proposed schedule due dates
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays);
|
||||
final List<LocalDate> scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
// 2. determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
+31
-24
@@ -11,13 +11,15 @@ import java.util.List;
|
||||
import org.joda.time.LocalDate;
|
||||
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.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
@Override
|
||||
public List<LocalDate> generate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays) {
|
||||
final List<Holiday> holidays, final WorkingDays workingDays) {
|
||||
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
@@ -29,44 +31,49 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
||||
|
||||
LocalDate dueRepaymentPeriodDate = startDate;
|
||||
|
||||
if (repaymentPeriod == 1 && firstRepaymentPeriodDate != null) {
|
||||
dueRepaymentPeriodDate = firstRepaymentPeriodDate;
|
||||
} else {
|
||||
|
||||
final int repaidEvery = loanApplicationTerms.getRepaymentEvery();
|
||||
|
||||
switch (loanApplicationTerms.getRepaymentPeriodFrequencyType()) {
|
||||
case DAYS:
|
||||
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
|
||||
break;
|
||||
case YEARS:
|
||||
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, startDate);
|
||||
}
|
||||
|
||||
//start date should be assigned before applying holidays to avoid the reset of next repayment dates.
|
||||
startDate = dueRepaymentPeriodDate;
|
||||
//Apply holidays
|
||||
LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms, dueRepaymentPeriodDate);
|
||||
|
||||
dueRepaymentPeriodDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(dueRepaymentPeriodDate, nextDueRepaymentPeriodDate, workingDays);
|
||||
|
||||
if(isHolidayEnabled){
|
||||
dueRepaymentPeriodDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(dueRepaymentPeriodDate, holidays);
|
||||
}
|
||||
|
||||
|
||||
dueRepaymentPeriodDates.add(dueRepaymentPeriodDate);
|
||||
}
|
||||
|
||||
return dueRepaymentPeriodDates;
|
||||
}
|
||||
|
||||
private LocalDate getRepaymentPeriodDate(final LoanApplicationTerms loanApplicationTerms, LocalDate startDate) {
|
||||
final int repaidEvery = loanApplicationTerms.getRepaymentEvery();
|
||||
LocalDate dueRepaymentPeriodDate = startDate;
|
||||
switch (loanApplicationTerms.getRepaymentPeriodFrequencyType()) {
|
||||
case DAYS:
|
||||
dueRepaymentPeriodDate = startDate.plusDays(repaidEvery);
|
||||
break;
|
||||
case WEEKS:
|
||||
dueRepaymentPeriodDate = startDate.plusWeeks(repaidEvery);
|
||||
break;
|
||||
case MONTHS:
|
||||
dueRepaymentPeriodDate = startDate.plusMonths(repaidEvery);
|
||||
break;
|
||||
case YEARS:
|
||||
dueRepaymentPeriodDate = startDate.plusYears(repaidEvery);
|
||||
break;
|
||||
case INVALID:
|
||||
break;
|
||||
}
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(final PeriodFrequencyType repaymentPeriodFrequencyType,
|
||||
final int repaidEvery, final List<LocalDate> scheduledDates) {
|
||||
|
||||
+2
-1
@@ -11,10 +11,11 @@ import java.util.Set;
|
||||
|
||||
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.domain.LoanCharge;
|
||||
|
||||
public interface LoanScheduleGenerator {
|
||||
|
||||
LoanScheduleModel generate(MathContext mc, ApplicationCurrency applicationCurrency, LoanApplicationTerms loanApplicationTerms,
|
||||
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays);
|
||||
Set<LoanCharge> loanCharges, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays);
|
||||
}
|
||||
+2
-1
@@ -9,11 +9,12 @@ 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.loanproduct.domain.PeriodFrequencyType;
|
||||
|
||||
public interface ScheduledDateGenerator {
|
||||
|
||||
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays);
|
||||
List<LocalDate> generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List<Holiday> holidays, final WorkingDays workingDays);
|
||||
|
||||
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
|
||||
List<LocalDate> scheduledDates);
|
||||
|
||||
+33
-6
@@ -16,10 +16,14 @@ import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomain
|
||||
import org.mifosplatform.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.mifosplatform.organisation.holiday.domain.Holiday;
|
||||
import org.mifosplatform.organisation.holiday.domain.HolidayRepository;
|
||||
import org.mifosplatform.organisation.holiday.service.HolidayUtil;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
||||
@@ -64,6 +68,7 @@ public class LoanScheduleAssembler {
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final GroupRepositoryWrapper groupRepository;
|
||||
private final WorkingDaysRepositoryWrapper workingDaysRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||
@@ -71,7 +76,8 @@ public class LoanScheduleAssembler {
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
|
||||
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
|
||||
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository) {
|
||||
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
|
||||
final WorkingDaysRepositoryWrapper workingDaysRepository) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.applicationCurrencyRepository = applicationCurrencyRepository;
|
||||
@@ -83,6 +89,7 @@ public class LoanScheduleAssembler {
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.clientRepository = clientRepository;
|
||||
this.groupRepository = groupRepository;
|
||||
this.workingDaysRepository = workingDaysRepository;
|
||||
}
|
||||
|
||||
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
|
||||
@@ -182,7 +189,7 @@ public class LoanScheduleAssembler {
|
||||
expectedDisbursementDate, repaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment,
|
||||
graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney);
|
||||
}
|
||||
|
||||
|
||||
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
|
||||
if (repaymentsStartingFromDate != null
|
||||
&& !CalendarHelper.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
|
||||
@@ -221,7 +228,7 @@ public class LoanScheduleAssembler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
|
||||
LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
|
||||
return loanApplicationTerms.toLoanProductRelatedDetail();
|
||||
@@ -249,11 +256,16 @@ public class LoanScheduleAssembler {
|
||||
|
||||
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed("expectedDisbursementDate", element);
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, expectedDisbursementDate.toDate());
|
||||
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, element);
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
|
||||
validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
|
||||
validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays);
|
||||
|
||||
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element);
|
||||
}
|
||||
|
||||
public LoanScheduleModel assembleLoanScheduleFrom(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled,
|
||||
final List<Holiday> holidays, final JsonElement element) {
|
||||
final List<Holiday> holidays, final WorkingDays workingDays, final JsonElement element) {
|
||||
|
||||
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element);
|
||||
|
||||
@@ -264,7 +276,22 @@ public class LoanScheduleAssembler {
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
|
||||
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays, workingDays);
|
||||
}
|
||||
|
||||
private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate disbursementDate, final WorkingDays workingDays) {
|
||||
if(!WorkingDaysUtil.isWorkingDay(workingDays, disbursementDate)){
|
||||
final String errorMessage = "The expected disbursement date cannot be on a non working day";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, disbursementDate);
|
||||
}
|
||||
}
|
||||
|
||||
return loanScheduleGenerator.generate(mc, applicationCurrency, loanApplicationTerms, loanCharges, isHolidayEnabled, holidays);
|
||||
private void validateDisbursementDateIsOnHoliday(final LocalDate disbursementDate, final boolean isHolidayEnabled, final List<Holiday> holidays) {
|
||||
if (isHolidayEnabled) {
|
||||
if (HolidayUtil.isHoliday(disbursementDate, holidays)) {
|
||||
final String errorMessage = "The expected disbursement date cannot be on a holiday";
|
||||
throw new LoanApplicationDateException("disbursement.date.on.holiday", errorMessage, disbursementDate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -22,6 +22,8 @@ import org.mifosplatform.organisation.staff.domain.Staff;
|
||||
import org.mifosplatform.organisation.staff.domain.StaffRepository;
|
||||
import org.mifosplatform.organisation.staff.exception.StaffNotFoundException;
|
||||
import org.mifosplatform.organisation.staff.exception.StaffRoleException;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.portfolio.client.domain.Client;
|
||||
import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.mifosplatform.portfolio.collateral.domain.LoanCollateral;
|
||||
@@ -76,6 +78,7 @@ public class LoanAssembler {
|
||||
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final WorkingDaysRepositoryWrapper workingDaysRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
|
||||
@@ -84,8 +87,9 @@ public class LoanAssembler {
|
||||
final StaffRepository staffRepository, final CodeValueRepositoryWrapper codeValueRepository,
|
||||
final LoanScheduleAssembler loanScheduleAssembler, final LoanChargeAssembler loanChargeAssembler,
|
||||
final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper,
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService) {
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
|
||||
final WorkingDaysRepositoryWrapper workingDaysRepository) {
|
||||
this.fromApiJsonHelper = fromApiJsonHelper;
|
||||
this.loanProductRepository = loanProductRepository;
|
||||
this.clientRepository = clientRepository;
|
||||
@@ -101,6 +105,7 @@ public class LoanAssembler {
|
||||
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.workingDaysRepository = workingDaysRepository;
|
||||
}
|
||||
|
||||
public Loan assembleFrom(final JsonCommand command, final AppUser currentUser) {
|
||||
@@ -184,10 +189,10 @@ public class LoanAssembler {
|
||||
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(), loanApplicationTerms.getExpectedDisbursementDate().toDate());
|
||||
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, element);
|
||||
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element);
|
||||
loanApplication.loanApplicationSubmittal(currentUser, loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine(),
|
||||
submittedOnDate, externalId);
|
||||
submittedOnDate, externalId, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
return loanApplication;
|
||||
}
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ public interface LoanWritePlatformService {
|
||||
|
||||
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
|
||||
|
||||
void updateRepaymentsSchedule(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
|
||||
void applyMeetingDateChanges(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
|
||||
|
||||
void applyHolidaysToLoans();
|
||||
}
|
||||
+30
-17
@@ -32,6 +32,8 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||
import org.mifosplatform.organisation.office.domain.Office;
|
||||
import org.mifosplatform.organisation.staff.domain.Staff;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
|
||||
import org.mifosplatform.organisation.workingdays.domain.WorkingDays;
|
||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||
@@ -103,6 +105,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
private final PaymentDetailWritePlatformService paymentDetailWritePlatformService;
|
||||
private final HolidayRepository holidayRepository;
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final WorkingDaysRepositoryWrapper workingDaysRepository;
|
||||
|
||||
@Autowired
|
||||
public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context,
|
||||
@@ -115,7 +118,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
|
||||
final LoanScheduleGeneratorFactory loanScheduleFactory, final CalendarInstanceRepository calendarInstanceRepository,
|
||||
final PaymentDetailWritePlatformService paymentDetailWritePlatformService, final HolidayRepository holidayRepository,
|
||||
final ConfigurationDomainService configurationDomainService) {
|
||||
final ConfigurationDomainService configurationDomainService, final WorkingDaysRepositoryWrapper workingDaysRepository) {
|
||||
|
||||
this.context = context;
|
||||
this.loanEventApiJsonValidator = loanEventApiJsonValidator;
|
||||
this.loanAssembler = loanAssembler;
|
||||
@@ -134,6 +138,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
this.paymentDetailWritePlatformService = paymentDetailWritePlatformService;
|
||||
this.holidayRepository = holidayRepository;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.workingDaysRepository = workingDaysRepository;
|
||||
}
|
||||
|
||||
private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
|
||||
@@ -173,13 +178,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final LocalDate firstRepaymentMeetingDate = getFirstRepaymentMeetingDate(actualDisbursementDate, loan, calendarInstance);
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(),
|
||||
actualDisbursementDate.toDate());
|
||||
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
updateLoanCounters(loan, actualDisbursementDate);
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate, isHolidayEnabled, holidays);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentMeetingDate, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -262,10 +266,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
// assuming repayment schedule won't regenerate because expected
|
||||
// disbursement and actual disbursement happens on same date
|
||||
final LocalDate firstRepaymentOnDate = null;
|
||||
final boolean isHolidayEnabled = false;
|
||||
final List<Holiday> holidays = null;
|
||||
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), actualDisbursementDate.toDate());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
|
||||
loan.disburse(this.loanScheduleFactory, currentUser, command, applicationCurrency, existingTransactionIds,
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate, isHolidayEnabled, holidays);
|
||||
existingReversedTransactionIds, changes, paymentDetail, firstRepaymentOnDate, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
this.loanRepository.save(loan);
|
||||
@@ -376,9 +383,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
final Money repaymentAmount = Money.of(loan.getCurrency(), transactionAmount);
|
||||
final LoanTransaction newRepaymentTransaction = LoanTransaction.repayment(repaymentAmount, paymentDetail, transactionDate);
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), transactionDate.toDate());
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
|
||||
final ChangedTransactionDetail changedTransactionDetail = loan.makeRepayment(newRepaymentTransaction,
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds);
|
||||
defaultLoanLifecycleStateMachine(), existingTransactionIds, existingReversedTransactionIds, isHolidayEnabled, holidays, workingDays);
|
||||
|
||||
this.loanTransactionRepository.save(newRepaymentTransaction);
|
||||
|
||||
@@ -1011,9 +1021,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void updateRepaymentsSchedule(final Calendar calendar, Collection<CalendarInstance> loanCalendarInstances) {
|
||||
|
||||
public void applyMeetingDateChanges(final Calendar calendar, Collection<CalendarInstance> loanCalendarInstances) {
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
final WorkingDays workingDays = this.workingDaysRepository.findOne();
|
||||
final Collection<Integer> loanStatuses = new ArrayList<Integer>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
||||
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
||||
final Collection<Integer> loanTypes = new ArrayList<Integer>(Arrays.asList(LoanType.GROUP.getValue(), LoanType.JLG.getValue()));
|
||||
@@ -1029,8 +1040,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
for (Loan loan : loans) {
|
||||
if (loan != null) {
|
||||
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
|
||||
holidays);
|
||||
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, holidays, workingDays);
|
||||
this.loanRepository.save(loan);
|
||||
}
|
||||
}
|
||||
@@ -1188,6 +1198,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
@CronTarget(jobName = JobName.APPLY_HOLIDAYS_TO_LOANS)
|
||||
public void applyHolidaysToLoans() {
|
||||
|
||||
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
|
||||
|
||||
if(!isHolidayEnabled) return;
|
||||
|
||||
final Collection<Integer> loanStatuses = new ArrayList<Integer>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(),
|
||||
LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue()));
|
||||
// Get all Holidays which are yet to applied to loans
|
||||
@@ -1206,9 +1220,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
||||
List<Loan> loans = new ArrayList<Loan>();
|
||||
// get all individual and jlg loans
|
||||
loans.addAll(this.loanRepository.findByClientOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||
// FIXME: optimize to get all client and group loans belongs to a
|
||||
// office id
|
||||
// get all group loans
|
||||
//FIXME: AA optimize to get all client and group loans belongs to a office id
|
||||
//get all group loans
|
||||
loans.addAll(this.loanRepository.findByGroupOfficeIdsAndLoanStatus(officeIds, loanStatuses));
|
||||
|
||||
for (Loan loan : loans) {
|
||||
|
||||
Reference in New Issue
Block a user