From dcad3955e19acc4760829367ce48e7b885f1c762 Mon Sep 17 00:00:00 2001 From: ashok-conflux Date: Thu, 11 Jul 2013 18:07:25 +0530 Subject: [PATCH] MIFOSX-543: Implement working days for Loans --- .../core_db/V83__non-working-days-table.sql | 11 +++ .../holiday/service/HolidayUtil.java | 10 +++ ...WritePlatformServiceJpaRepositoryImpl.java | 8 +- .../domain/RepaymentRescheduleType.java | 72 ++++++++++++++++ .../workingdays/domain/WorkingDays.java | 50 +++++++++++ .../domain/WorkingDaysRepository.java | 13 +++ .../domain/WorkingDaysRepositoryWrapper.java | 50 +++++++++++ .../WorkingDaysNotFoundException.java | 18 ++++ .../workingdays/service/WorkingDaysUtil.java | 43 ++++++++++ .../calendar/service/CalendarHelper.java | 39 +++++---- ...WritePlatformServiceJpaRepositoryImpl.java | 2 +- .../portfolio/loanaccount/domain/Loan.java | 83 +++++++++++++++---- .../domain/AbstractLoanScheduleGenerator.java | 6 +- .../domain/DefaultScheduledDateGenerator.java | 55 ++++++------ .../domain/LoanScheduleGenerator.java | 3 +- .../domain/ScheduledDateGenerator.java | 3 +- .../service/LoanScheduleAssembler.java | 39 +++++++-- .../loanaccount/service/LoanAssembler.java | 15 ++-- .../service/LoanWritePlatformService.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 47 +++++++---- 20 files changed, 475 insertions(+), 94 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V83__non-working-days-table.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/RepaymentRescheduleType.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDays.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepository.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/exception/WorkingDaysNotFoundException.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java diff --git a/mifosng-db/migrations/core_db/V83__non-working-days-table.sql b/mifosng-db/migrations/core_db/V83__non-working-days-table.sql new file mode 100644 index 000000000..475a0eefb --- /dev/null +++ b/mifosng-db/migrations/core_db/V83__non-working-days-table.sql @@ -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); \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayUtil.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayUtil.java index 47c366811..df5526119 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayUtil.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayUtil.java @@ -17,4 +17,14 @@ public class HolidayUtil { } return repaymentDate; } + + public static boolean isHoliday(final LocalDate date, final List 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; + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java index 019ffc5be..8618a15e0 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/holiday/service/HolidayWritePlatformServiceJpaRepositoryImpl.java @@ -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()); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/RepaymentRescheduleType.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/RepaymentRescheduleType.java new file mode 100644 index 000000000..2a992312c --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/RepaymentRescheduleType.java @@ -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 intToEnumMap = new HashMap(); + 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(); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDays.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDays.java new file mode 100644 index 000000000..e7225f9df --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDays.java @@ -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 { + + @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; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepository.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepository.java new file mode 100644 index 000000000..9b06f8701 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepository.java @@ -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, JpaSpecificationExecutor { + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java new file mode 100644 index 000000000..f17d12d21 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/domain/WorkingDaysRepositoryWrapper.java @@ -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; + +/** + *

+ * Wrapper for {@link WorkingDaysRepository} that adds NULL checking and Error + * handling capabilities + *

+ */ +@Service +public class WorkingDaysRepositoryWrapper { + + private final WorkingDaysRepository repository; + + @Autowired + public WorkingDaysRepositoryWrapper(final WorkingDaysRepository repository) { + this.repository = repository; + } + + public WorkingDays findOne() { + final List 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); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/exception/WorkingDaysNotFoundException.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/exception/WorkingDaysNotFoundException.java new file mode 100644 index 000000000..26a4ddf50 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/exception/WorkingDaysNotFoundException.java @@ -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."); + } +} \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java new file mode 100644 index 000000000..56a4a5b02 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/organisation/workingdays/service/WorkingDaysUtil.java @@ -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); + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java index 967dc0b1f..6c3f1e37d 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarHelper.java @@ -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 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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index 84f9116d3..ad59d6e4b 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -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); // } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index aae141811..9b466c798 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -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 { 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 holidays, + final WorkingDays workingDays) { updateLoanSchedule(loanSchedule); @@ -1119,8 +1123,12 @@ public class Loan extends AbstractPersistable { 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 { public Map disburse(final LoanScheduleGeneratorFactory loanScheduleFactory, final AppUser currentUser, final JsonCommand command, final ApplicationCurrency currency, final List existingTransactionIds, final List existingReversedTransactionIds, final Map actualChanges, final PaymentDetail paymentDetail, - final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, final List holidays) { + final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, final List holidays, final WorkingDays workingDays) { updateLoanToPreDisbursalState(); @@ -1332,10 +1340,14 @@ public class Loan extends AbstractPersistable { 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 { */ private void regenerateRepaymentSchedule(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency, final LocalDate firstRepaymentMeetingDate, final boolean isHolidayEnabled, - final List holidays) { + final List 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 { 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 { public ChangedTransactionDetail makeRepayment(final LoanTransaction repaymentTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final List existingTransactionIds, - final List existingReversedTransactionIds) { + final List existingReversedTransactionIds, final boolean isHolidayEnabled, final List 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 { } public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, - final boolean isHolidayEnabled, final List 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 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 { 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 { 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 { } } + 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 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 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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index de3756349..b2327b98a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -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 loanCharges, final boolean isHolidayEnabled, final List holidays) { + final LoanApplicationTerms loanApplicationTerms, final Set loanCharges, final boolean isHolidayEnabled, + final List holidays, final WorkingDays workingDays) { // 1. generate list of proposed schedule due dates - final List scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays); + final List scheduledDates = this.scheduledDateGenerator.generate(loanApplicationTerms, isHolidayEnabled, holidays, workingDays); // 2. determine the total charges due at time of disbursement final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 5681a843a..4bde5c0cc 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -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 generate(final LoanApplicationTerms loanApplicationTerms, final boolean isHolidayEnabled, - final List holidays) { + final List 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 scheduledDates) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index 2457b7f4e..594d88d9a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -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 loanCharges, boolean isHolidayEnabled, List holidays); + Set loanCharges, boolean isHolidayEnabled, List holidays, final WorkingDays workingDays); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index e2a8756b3..4c54c29c7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -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 generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays); + List generate(LoanApplicationTerms loanApplicationTerms, boolean isHolidayEnabled, List holidays, final WorkingDays workingDays); LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, List scheduledDates); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 179e7d84d..1ea4588ac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -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 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 holidays, final JsonElement element) { + final List holidays, final WorkingDays workingDays, final JsonElement element) { final Set 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 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); + } + } } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java index ed106833b..cf04003f3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanAssembler.java @@ -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 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; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java index e36397614..e410610ed 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformService.java @@ -51,7 +51,7 @@ public interface LoanWritePlatformService { CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command); - void updateRepaymentsSchedule(Calendar calendar, Collection loanCalendarInstances); + void applyMeetingDateChanges(Calendar calendar, Collection loanCalendarInstances); void applyHolidaysToLoans(); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index f13a553a5..a139f20e7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -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 holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), - actualDisbursementDate.toDate()); - + final List 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 holidays = null; + final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + final List 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 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 loanCalendarInstances) { - + public void applyMeetingDateChanges(final Calendar calendar, Collection loanCalendarInstances) { + final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); + final WorkingDays workingDays = this.workingDaysRepository.findOne(); final Collection loanStatuses = new ArrayList(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue())); final Collection loanTypes = new ArrayList(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 loanStatuses = new ArrayList(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 loans = new ArrayList(); // 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) {