Merge remote-tracking branch 'ashok/MIFOSX-475' into develop
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
INSERT INTO `c_configuration` (`name`, `enabled`) VALUES ('reschedule-future-repayments', 1);
|
||||||
+1
@@ -9,4 +9,5 @@ public interface ConfigurationDomainService {
|
|||||||
|
|
||||||
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
boolean isMakerCheckerEnabledForTask(String taskPermissionCode);
|
||||||
boolean isAmazonS3Enabled();
|
boolean isAmazonS3Enabled();
|
||||||
|
boolean isRescheduleFutureRepaymentsEnabled();
|
||||||
}
|
}
|
||||||
+9
@@ -44,4 +44,13 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
|
|||||||
public boolean isAmazonS3Enabled(){
|
public boolean isAmazonS3Enabled(){
|
||||||
return this.globalConfigurationRepository.findOneByName("amazon-S3").isEnabled();
|
return this.globalConfigurationRepository.findOneByName("amazon-S3").isEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isRescheduleFutureRepaymentsEnabled() {
|
||||||
|
final String rescheduleRepaymentsConfigurationProperty = "reschedule-future-repayments";
|
||||||
|
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByName(rescheduleRepaymentsConfigurationProperty);
|
||||||
|
if (property == null) { throw new GlobalConfigurationPropertyNotFoundException(rescheduleRepaymentsConfigurationProperty); }
|
||||||
|
return property.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+43
-6
@@ -21,6 +21,9 @@ import org.joda.time.LocalDate;
|
|||||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
|
import org.mifosplatform.infrastructure.core.domain.AbstractAuditableCustom;
|
||||||
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
|
import org.mifosplatform.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS;
|
||||||
|
import org.mifosplatform.portfolio.calendar.exception.CalendarDateException;
|
||||||
|
import org.mifosplatform.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException;
|
||||||
|
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||||
import org.mifosplatform.useradministration.domain.AppUser;
|
import org.mifosplatform.useradministration.domain.AppUser;
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@@ -147,13 +150,19 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
final String localeAsInput = command.locale();
|
final String localeAsInput = command.locale();
|
||||||
final String startDateParamName = CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue();
|
final String startDateParamName = CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue();
|
||||||
if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDateLocalDate())) {
|
if (command.isChangeInLocalDateParameterNamed(startDateParamName, getStartDateLocalDate())) {
|
||||||
final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
|
|
||||||
actualChanges.put(startDateParamName, valueAsInput);
|
|
||||||
actualChanges.put("dateFormat", dateFormatAsInput);
|
|
||||||
actualChanges.put("locale", localeAsInput);
|
|
||||||
|
|
||||||
|
final String valueAsInput = command.stringValueOfParameterNamed(startDateParamName);
|
||||||
final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
|
final LocalDate newValue = command.localDateValueOfParameterNamed(startDateParamName);
|
||||||
this.startDate = newValue.toDate();
|
if(isStartDateBefore(newValue)){
|
||||||
|
actualChanges.put(startDateParamName, valueAsInput);
|
||||||
|
actualChanges.put("dateFormat", dateFormatAsInput);
|
||||||
|
actualChanges.put("locale", localeAsInput);
|
||||||
|
this.startDate = newValue.toDate();
|
||||||
|
}else{
|
||||||
|
//new meeting start date should be greater than existing meeting start date
|
||||||
|
final String defaultUserMessage = "New meeting start on or after date cannot be a date before existing meeting start date";
|
||||||
|
throw new CalendarDateException("new.start.date.before.existing.date", defaultUserMessage, newValue, getStartDateLocalDate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final String endDateParamName = CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue();
|
final String endDateParamName = CALENDAR_SUPPORTED_PARAMETERS.END_DATE.getValue();
|
||||||
@@ -174,11 +183,19 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
this.duration = newValue;
|
this.duration = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Do not allow to change calendar type
|
||||||
|
//TODO: AA Instead of throwing an exception, do not allow meeting calendar type to update.
|
||||||
final String typeParamName = CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue();
|
final String typeParamName = CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue();
|
||||||
if (command.isChangeInIntegerSansLocaleParameterNamed(typeParamName, this.typeId)) {
|
if (command.isChangeInIntegerSansLocaleParameterNamed(typeParamName, this.typeId)) {
|
||||||
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
|
final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
|
||||||
|
final String defaultUserMessage = "Meeting calendar type update is not supported";
|
||||||
|
final String oldMeeingType = CalendarType.fromInt(this.typeId).name();
|
||||||
|
final String newMeetingType = CalendarType.fromInt(newValue).name();
|
||||||
|
|
||||||
|
throw new CalendarParameterUpdateNotSupportedException("meeting.type", defaultUserMessage, newMeetingType, oldMeeingType);
|
||||||
|
/*final Integer newValue = command.integerValueSansLocaleOfParameterNamed(typeParamName);
|
||||||
actualChanges.put(typeParamName, newValue);
|
actualChanges.put(typeParamName, newValue);
|
||||||
this.typeId = newValue;
|
this.typeId = newValue;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
final String repeatingParamName = CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue();
|
final String repeatingParamName = CALENDAR_SUPPORTED_PARAMETERS.REPEATING.getValue();
|
||||||
@@ -188,9 +205,25 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
this.repeating = newValue;
|
this.repeating = newValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//FIXME: AA Construct recurrence rule in API rather than getting it from UI
|
||||||
final String recurrenceParamName = CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue();
|
final String recurrenceParamName = CALENDAR_SUPPORTED_PARAMETERS.RECURRENCE.getValue();
|
||||||
if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) {
|
if (command.isChangeInStringParameterNamed(recurrenceParamName, this.recurrence)) {
|
||||||
final String newValue = command.stringValueOfParameterNamed(recurrenceParamName);
|
final String newValue = command.stringValueOfParameterNamed(recurrenceParamName);
|
||||||
|
|
||||||
|
//FIXME: AA - Is this restriction required only for collection type meetings or for all?.
|
||||||
|
//Do not allow to change meeting frequency
|
||||||
|
|
||||||
|
if(!CalendarHelper.isFrequencySame(this.recurrence, newValue)){
|
||||||
|
final String defaultUserMessage = "Update of meeting frequency is not supported";
|
||||||
|
throw new CalendarParameterUpdateNotSupportedException("meeting.frequency", defaultUserMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Do not allow to change meeting interval
|
||||||
|
if(!CalendarHelper.isIntervalSame(this.recurrence, newValue)){
|
||||||
|
final String defaultUserMessage = "Update of meeting interval is not supported";
|
||||||
|
throw new CalendarParameterUpdateNotSupportedException("meeting.interval", defaultUserMessage);
|
||||||
|
}
|
||||||
|
|
||||||
actualChanges.put(recurrenceParamName, newValue);
|
actualChanges.put(recurrenceParamName, newValue);
|
||||||
this.recurrence = StringUtils.defaultIfEmpty(newValue, null);
|
this.recurrence = StringUtils.defaultIfEmpty(newValue, null);
|
||||||
}
|
}
|
||||||
@@ -283,4 +316,8 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
|
|||||||
return endDateLocalDate;
|
return endDateLocalDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isStartDateBefore(final LocalDate newStartDate) {
|
||||||
|
if (this.startDate != null && newStartDate != null && getStartDateLocalDate().isBefore(newStartDate)) { return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
@@ -20,4 +20,6 @@ public interface CalendarInstanceRepository extends JpaRepository<CalendarInstan
|
|||||||
|
|
||||||
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
@Query("from CalendarInstance ci where ci.entityId = :loanId and ci.entityTypeId = :entityTypeId")
|
||||||
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
CalendarInstance findCalendarInstaneByLoanId(@Param("loanId") Long loanId, @Param("entityTypeId") Integer entityTypeId);
|
||||||
|
|
||||||
|
Collection<CalendarInstance> findByCalendarIdAndEntityTypeId(Long calendarId, Integer entityTypeId);
|
||||||
}
|
}
|
||||||
|
|||||||
+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.portfolio.calendar.exception;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RuntimeException} thrown when Calendar resources are not found.
|
||||||
|
*/
|
||||||
|
public class CalendarDateException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CalendarDateException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
|
||||||
|
super("error.msg.calendar." + postFix , defaultUserMessage, defaultUserMessageArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.portfolio.calendar.exception;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.core.exception.AbstractPlatformDomainRuleException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link RuntimeException} thrown when Calendar resources are not found.
|
||||||
|
*/
|
||||||
|
public class CalendarParameterUpdateNotSupportedException extends AbstractPlatformDomainRuleException {
|
||||||
|
|
||||||
|
public CalendarParameterUpdateNotSupportedException(String postFix, String defaultUserMessage, Object... defaultUserMessageArgs) {
|
||||||
|
super("error.msg.calendar.update.of." + postFix + ".is.not.supported", defaultUserMessage, defaultUserMessageArgs);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -221,6 +221,7 @@ public class CalendarCommandFromApiJsonDeserializer extends AbstractFromApiJsonD
|
|||||||
CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element);
|
CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue(), element);
|
||||||
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue()).value(duration).ignoreIfNull();
|
baseDataValidator.reset().parameter(CALENDAR_SUPPORTED_PARAMETERS.DURATION.getValue()).value(duration).ignoreIfNull();
|
||||||
}
|
}
|
||||||
|
//TODO: AA do not allow to change calendar type.
|
||||||
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(), element)) {
|
if (this.fromApiJsonHelper.parameterExists(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(), element)) {
|
||||||
final Integer typeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(),
|
final Integer typeId = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(CALENDAR_SUPPORTED_PARAMETERS.TYPE_ID.getValue(),
|
||||||
element);
|
element);
|
||||||
|
|||||||
+75
-38
@@ -33,28 +33,31 @@ import org.mifosplatform.portfolio.loanproduct.domain.PeriodFrequencyType;
|
|||||||
public class CalendarHelper {
|
public class CalendarHelper {
|
||||||
|
|
||||||
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
|
public static LocalDate getNextRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate startDate) {
|
||||||
|
|
||||||
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
|
||||||
if (recur == null) { return null; }
|
if (recur == null) { return null; }
|
||||||
|
return getNextRecurringDate(recur, seedDate, startDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalDate getNextRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate startDate) {
|
||||||
final DateTime periodStart = new DateTime(startDate.toDate());
|
final DateTime periodStart = new DateTime(startDate.toDate());
|
||||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");//Date format in iCal4J is hard coded
|
final Date seed = convertToiCal4JCompatibleDate(seedDate);
|
||||||
final String seedDateStr = df.format(seedDate.toDateTimeAtStartOfDay().toDate());
|
|
||||||
|
|
||||||
Date seed = null;
|
|
||||||
try {
|
|
||||||
seed = new Date(seedDateStr, "yyyy-MM-dd");
|
|
||||||
} catch (ParseException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
final Date nextRecDate = recur.getNextDate(seed, periodStart);
|
final Date nextRecDate = recur.getNextDate(seed, periodStart);
|
||||||
return nextRecDate == null ? null : new LocalDate(nextRecDate);
|
return nextRecDate == null ? null : new LocalDate(nextRecDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Date convertToiCal4JCompatibleDate(final LocalDate inputDate){
|
||||||
|
// Date format in iCal4J is hard coded
|
||||||
|
Date formattedDate = null;
|
||||||
|
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
||||||
|
final String seedDateStr = df.format(inputDate.toDateTimeAtStartOfDay().toDate());
|
||||||
|
try {
|
||||||
|
formattedDate = new Date(seedDateStr, "yyyy-MM-dd");
|
||||||
|
} catch (ParseException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return formattedDate;
|
||||||
|
}
|
||||||
|
|
||||||
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) {
|
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate, final LocalDate endDate) {
|
||||||
|
|
||||||
final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant();
|
final LocalDate periodStartDate = DateUtils.getLocalDateOfTenant();
|
||||||
@@ -79,16 +82,14 @@ public class CalendarHelper {
|
|||||||
|
|
||||||
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
|
||||||
|
return getRecurringDates(recur, seedDate, periodStartDate,
|
||||||
|
periodEndDate, maxCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<LocalDate> getRecurringDates(final Recur recur, final LocalDate seedDate, final LocalDate periodStartDate,
|
||||||
|
final LocalDate periodEndDate, final int maxCount) {
|
||||||
if (recur == null) { return null; }
|
if (recur == null) { return null; }
|
||||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
|
final Date seed = convertToiCal4JCompatibleDate(seedDate);
|
||||||
final String seedDateStr = df.format(seedDate.toDateTimeAtStartOfDay().toDate());
|
|
||||||
Date seed = null;
|
|
||||||
try {
|
|
||||||
seed = new Date(seedDateStr, "yyyy-MM-dd");
|
|
||||||
} catch (ParseException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
final DateTime periodStart = new DateTime(periodStartDate.toDate());
|
final DateTime periodStart = new DateTime(periodStartDate.toDate());
|
||||||
final DateTime periodEnd = new DateTime(periodEndDate.toDate());
|
final DateTime periodEnd = new DateTime(periodEndDate.toDate());
|
||||||
|
|
||||||
@@ -207,7 +208,15 @@ public class CalendarHelper {
|
|||||||
|
|
||||||
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){
|
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date){
|
||||||
|
|
||||||
final Collection<LocalDate> recurDate = getRecurringDates(recurringRule, seedDate, date, date.plusDays(1), 1);
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
if (recur == null) { return false; }
|
||||||
|
|
||||||
|
return isValidRedurringDate(recur, seedDate, date);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isValidRedurringDate(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;
|
return (recurDate == null || recurDate.isEmpty()) ? false : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,21 +297,49 @@ public class CalendarHelper {
|
|||||||
recur.setFrequency(frequency);
|
recur.setFrequency(frequency);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DateTime periodStart = new DateTime(startDate.toDate());
|
final LocalDate firstRepaymentDate = getNextRecurringDate(recur, seedDate, startDate);
|
||||||
final DateFormat df = new SimpleDateFormat("yyyy-MM-dd");//Date format in iCal4J is hard coded
|
|
||||||
final String seedDateStr = df.format(seedDate.toDateTimeAtStartOfDay().toDate());
|
|
||||||
|
|
||||||
Date seed = null;
|
|
||||||
try {
|
|
||||||
seed = new Date(seedDateStr, "yyyy-MM-dd");
|
|
||||||
} catch (ParseException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Date nextRecDate = recur.getNextDate(seed, periodStart);
|
|
||||||
final LocalDate firstRepaymentDate = nextRecDate == null ? null : new LocalDate(nextRecDate);
|
|
||||||
|
|
||||||
return firstRepaymentDate;
|
return firstRepaymentDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate, final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency){
|
||||||
|
final Recur recur = CalendarHelper.getICalRecur(recurringRule);
|
||||||
|
if (recur == null) { return null; }
|
||||||
|
|
||||||
|
if(isValidRedurringDate(recur, seedDate, oldRepaymentDate)){
|
||||||
|
return oldRepaymentDate;
|
||||||
|
}
|
||||||
|
/* Recurring dates should follow loanRepaymentInterval.
|
||||||
|
e.g. The weekly meeting will have interval of 1, if the loan product with fortnightly frequency will have interval of 2,
|
||||||
|
to generate right set of meeting dates reset interval same as loan repayment interval.
|
||||||
|
*/
|
||||||
|
recur.setInterval(loanRepaymentInterval);
|
||||||
|
|
||||||
|
/*Recurring dates should follow loanRepayment frequency.
|
||||||
|
//e.g. daily meeting frequency should support all loan products with any type of frequency.
|
||||||
|
to generate right set of meeting dates reset frequency same as loan repayment frequency.
|
||||||
|
*/
|
||||||
|
if(recur.getFrequency().equals(Recur.DAILY)){
|
||||||
|
recur.setFrequency(frequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
final LocalDate newRepaymentDate = getNextRecurringDate(recur, seedDate, oldRepaymentDate);
|
||||||
|
return newRepaymentDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isFrequencySame(final String oldRRule, final String newRRule) {
|
||||||
|
Recur oldRecur = getICalRecur(oldRRule);
|
||||||
|
Recur newRecur = getICalRecur(newRRule);
|
||||||
|
|
||||||
|
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
|
||||||
|
return oldRecur.getFrequency().equals(newRecur.getFrequency());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isIntervalSame(final String oldRRule, final String newRRule) {
|
||||||
|
Recur oldRecur = getICalRecur(oldRRule);
|
||||||
|
Recur newRecur = getICalRecur(newRRule);
|
||||||
|
|
||||||
|
if (oldRecur == null || oldRecur.getFrequency() == null || newRecur == null || newRecur.getFrequency() == null) { return false; }
|
||||||
|
return (oldRecur.getInterval() == newRecur.getInterval());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-1
@@ -5,19 +5,24 @@
|
|||||||
*/
|
*/
|
||||||
package org.mifosplatform.portfolio.calendar.service;
|
package org.mifosplatform.portfolio.calendar.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.mifosplatform.infrastructure.configuration.domain.ConfigurationDomainService;
|
||||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository;
|
||||||
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
import org.mifosplatform.portfolio.calendar.domain.CalendarRepository;
|
||||||
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException;
|
||||||
import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer;
|
import org.mifosplatform.portfolio.calendar.serialization.CalendarCommandFromApiJsonDeserializer;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.service.LoanWritePlatformService;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
|
public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWritePlatformService {
|
||||||
@@ -25,14 +30,18 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
|
|||||||
private final CalendarRepository calendarRepository;
|
private final CalendarRepository calendarRepository;
|
||||||
private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
private final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||||
private final CalendarInstanceRepository calendarInstanceRepository;
|
private final CalendarInstanceRepository calendarInstanceRepository;
|
||||||
|
private final LoanWritePlatformService loanWritePlatformService;
|
||||||
|
private final ConfigurationDomainService configurationDomainService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
|
public CalendarWritePlatformServiceJpaRepositoryImpl(final CalendarRepository calendarRepository,
|
||||||
final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
final CalendarCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||||
final CalendarInstanceRepository calendarInstanceRepository) {
|
final CalendarInstanceRepository calendarInstanceRepository, final LoanWritePlatformService loanWritePlatformService, final ConfigurationDomainService configurationDomainService) {
|
||||||
this.calendarRepository = calendarRepository;
|
this.calendarRepository = calendarRepository;
|
||||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||||
this.calendarInstanceRepository = calendarInstanceRepository;
|
this.calendarInstanceRepository = calendarInstanceRepository;
|
||||||
|
this.loanWritePlatformService = loanWritePlatformService;
|
||||||
|
this.configurationDomainService = configurationDomainService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -66,6 +75,18 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr
|
|||||||
|
|
||||||
if (!changes.isEmpty()) {
|
if (!changes.isEmpty()) {
|
||||||
this.calendarRepository.saveAndFlush(calendarForUpdate);
|
this.calendarRepository.saveAndFlush(calendarForUpdate);
|
||||||
|
|
||||||
|
if(this.configurationDomainService.isRescheduleFutureRepaymentsEnabled()){
|
||||||
|
//In this approach loans following this meeting calendar will be affected immediately.
|
||||||
|
//get all calendar instances following this meeting calendar and calendar entity type is loan
|
||||||
|
final Collection<CalendarInstance> loanCalendarInstances = this.calendarInstanceRepository.findByCalendarIdAndEntityTypeId(calendarId, CalendarEntityType.LOANS.getValue());
|
||||||
|
|
||||||
|
if (!CollectionUtils.isEmpty(loanCalendarInstances)) {
|
||||||
|
// update all loans which are following this meeting calendar
|
||||||
|
this.loanWritePlatformService.updateRepaymentsSchedule(calendarForUpdate, loanCalendarInstances);
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new CommandProcessingResultBuilder() //
|
return new CommandProcessingResultBuilder() //
|
||||||
|
|||||||
+25
-1
@@ -51,6 +51,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
|
|||||||
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
|
||||||
import org.mifosplatform.organisation.monetary.domain.Money;
|
import org.mifosplatform.organisation.monetary.domain.Money;
|
||||||
import org.mifosplatform.organisation.staff.domain.Staff;
|
import org.mifosplatform.organisation.staff.domain.Staff;
|
||||||
|
import org.mifosplatform.portfolio.calendar.service.CalendarHelper;
|
||||||
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
import org.mifosplatform.portfolio.charge.exception.LoanChargeCannotBeAddedException;
|
||||||
import org.mifosplatform.portfolio.client.domain.Client;
|
import org.mifosplatform.portfolio.client.domain.Client;
|
||||||
import org.mifosplatform.portfolio.collateral.data.CollateralData;
|
import org.mifosplatform.portfolio.collateral.data.CollateralData;
|
||||||
@@ -190,7 +191,6 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
@JoinColumn(name = "disbursedon_userid", nullable = true)
|
@JoinColumn(name = "disbursedon_userid", nullable = true)
|
||||||
private AppUser disbursedBy;
|
private AppUser disbursedBy;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
@Temporal(TemporalType.DATE)
|
@Temporal(TemporalType.DATE)
|
||||||
@Column(name = "closedon_date")
|
@Column(name = "closedon_date")
|
||||||
private Date closedOnDate;
|
private Date closedOnDate;
|
||||||
@@ -2325,4 +2325,28 @@ public class Loan extends AbstractPersistable<Long> {
|
|||||||
return this.closedOnDate;
|
return this.closedOnDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule) {
|
||||||
|
//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();
|
||||||
|
//FIXME: AA this won't update repayment dates before current date.
|
||||||
|
if (oldDueDate.isAfter(meetingStartDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
|
||||||
|
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);
|
||||||
|
loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate);
|
||||||
|
//reset from date to get actual daysInPeriod
|
||||||
|
loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate);
|
||||||
|
tmpFromDate = newRepaymentDate;//update with new repayment date
|
||||||
|
}else{
|
||||||
|
tmpFromDate = oldDueDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+14
-2
@@ -35,11 +35,11 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
|||||||
|
|
||||||
@Temporal(TemporalType.DATE)
|
@Temporal(TemporalType.DATE)
|
||||||
@Column(name = "fromdate", nullable = true)
|
@Column(name = "fromdate", nullable = true)
|
||||||
private final Date fromDate;
|
private Date fromDate;
|
||||||
|
|
||||||
@Temporal(TemporalType.DATE)
|
@Temporal(TemporalType.DATE)
|
||||||
@Column(name = "duedate", nullable = false)
|
@Column(name = "duedate", nullable = false)
|
||||||
private final Date dueDate;
|
private Date dueDate;
|
||||||
|
|
||||||
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
|
@Column(name = "principal_amount", scale = 6, precision = 19, nullable = true)
|
||||||
private BigDecimal principal;
|
private BigDecimal principal;
|
||||||
@@ -521,4 +521,16 @@ public final class LoanRepaymentScheduleInstallment extends AbstractAuditableCus
|
|||||||
this.obligationsMetOnDate = transactionDate.toDate();
|
this.obligationsMetOnDate = transactionDate.toDate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void updateDueDate(final LocalDate newDueDate){
|
||||||
|
if(newDueDate != null){
|
||||||
|
this.dueDate = newDueDate.toDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateFromDate(final LocalDate newFromDate){
|
||||||
|
if(newFromDate != null){
|
||||||
|
this.fromDate = newFromDate.toDate();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+4
@@ -5,6 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.mifosplatform.portfolio.loanaccount.domain;
|
package org.mifosplatform.portfolio.loanaccount.domain;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
@@ -19,4 +20,7 @@ public interface LoanRepository extends JpaRepository<Loan, Long>,
|
|||||||
|
|
||||||
@Query("from Loan loan where loan.client.id = :clientId")
|
@Query("from Loan loan where loan.client.id = :clientId")
|
||||||
List<Loan> findLoanByClientId(@Param("clientId") Long clientId);
|
List<Loan> findLoanByClientId(@Param("clientId") Long clientId);
|
||||||
|
|
||||||
|
@Query("from Loan loan where loan.id IN :ids and loan.loanStatus IN :loanStatuses and loan.loanType IN :loanTypes")
|
||||||
|
List<Loan> findByIdsAndLoanStatusAndLoanType(@Param("ids") Collection<Long> ids, @Param("loanStatuses") Collection<Integer> loanStatuses, @Param("loanTypes") Collection<Integer> loanTypes);
|
||||||
}
|
}
|
||||||
+5
@@ -5,10 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
package org.mifosplatform.portfolio.loanaccount.service;
|
package org.mifosplatform.portfolio.loanaccount.service;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
import org.mifosplatform.infrastructure.core.api.JsonCommand;
|
||||||
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
import org.mifosplatform.infrastructure.core.data.CommandProcessingResult;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.Calendar;
|
||||||
|
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
|
||||||
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
|
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkDisbursalCommand;
|
||||||
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
|
import org.mifosplatform.portfolio.collectionsheet.command.CollectionSheetBulkRepaymentCommand;
|
||||||
|
|
||||||
@@ -47,4 +50,6 @@ public interface LoanWritePlatformService {
|
|||||||
CommandProcessingResult bulkLoanReassignment(JsonCommand command);
|
CommandProcessingResult bulkLoanReassignment(JsonCommand command);
|
||||||
|
|
||||||
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
|
CommandProcessingResult removeLoanOfficer(Long loanId, JsonCommand command);
|
||||||
|
|
||||||
|
void updateRepaymentsSchedule(Calendar calendar, Collection<CalendarInstance> loanCalendarInstances);
|
||||||
}
|
}
|
||||||
+24
@@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.service;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -55,6 +56,7 @@ import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus;
|
|||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||||
|
import org.mifosplatform.portfolio.loanaccount.domain.LoanType;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException;
|
||||||
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentException;
|
||||||
@@ -982,4 +984,26 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
|
|||||||
existingTransactionIds, existingReversedTransactionIds);
|
existingTransactionIds, existingReversedTransactionIds);
|
||||||
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateRepaymentsSchedule(final Calendar calendar, Collection<CalendarInstance> loanCalendarInstances) {
|
||||||
|
|
||||||
|
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()));
|
||||||
|
final Collection<Long> loanIds = new ArrayList<Long>(loanCalendarInstances.size());
|
||||||
|
//loop through loanCalendarInstances to get loan ids
|
||||||
|
for (CalendarInstance calendarInstance : loanCalendarInstances) {
|
||||||
|
loanIds.add(calendarInstance.getEntityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Loan> loans = this.loanRepository.findByIdsAndLoanStatusAndLoanType(loanIds, loanStatuses, loanTypes);
|
||||||
|
//loop through each loan to reschedule the repayment dates
|
||||||
|
for (Loan loan : loans) {
|
||||||
|
if(loan != null){
|
||||||
|
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence());
|
||||||
|
this.loanRepository.save(loan);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user