Skip Repayment Date Falling On First Day of Month

This commit is contained in:
jinjurajan
2016-03-23 14:50:47 +05:30
parent 9c38078de3
commit bba8ca47be
29 changed files with 658 additions and 131 deletions
@@ -0,0 +1,151 @@
package org.apache.fineract.integrationtests;
import static org.junit.Assert.assertEquals;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import org.apache.fineract.integrationtests.common.CalendarHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
import org.apache.fineract.integrationtests.common.GroupHelper;
import org.apache.fineract.integrationtests.common.Utils;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.junit.Before;
import org.junit.Test;
import com.jayway.restassured.builder.RequestSpecBuilder;
import com.jayway.restassured.builder.ResponseSpecBuilder;
import com.jayway.restassured.http.ContentType;
import com.jayway.restassured.specification.RequestSpecification;
import com.jayway.restassured.specification.ResponseSpecification;
import junit.framework.Assert;
@SuppressWarnings({ "static-access", "rawtypes", "unchecked", "deprecation" })
public class SkipRepaymentOnMonthFirstTest {
private ResponseSpecification responseSpec;
private RequestSpecification requestSpec;
private GlobalConfigurationHelper globalConfigurationHelper;
private LoanTransactionHelper loanTransactionHelper;
private CalendarHelper calendarHelper;
@Before
public void setup() {
Utils.initializeRESTAssured();
this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
this.requestSpec.header("Authorization",
"Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
}
@Test
public void testSkippingRepaymentOnFirstDayOfMonth() {
this.globalConfigurationHelper = new GlobalConfigurationHelper(this.requestSpec, this.responseSpec);
// Retrieving All Global Configuration details
final ArrayList<HashMap> globalConfig = this.globalConfigurationHelper
.getAllGlobalConfigurations(this.requestSpec, this.responseSpec);
Assert.assertNotNull(globalConfig);
String configName = "skip-repayment-on-first-day-of-month";
boolean newBooleanValue = true;
for (Integer configIndex = 0; configIndex < (globalConfig.size()); configIndex++) {
if (globalConfig.get(configIndex).get("name").equals(configName)) {
String configId = (globalConfig.get(configIndex).get("id")).toString();
Integer updateConfigId = this.globalConfigurationHelper.updateEnabledFlagForGlobalConfiguration(
this.requestSpec, this.responseSpec, configId.toString(), newBooleanValue);
Assert.assertNotNull(updateConfigId);
break;
}
}
}
@Test
public void checkRepaymentSkipOnFirstDayOfMonth() {
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
groupID = GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(),
clientID.toString());
final String startDate = "15 September 2011";
final String frequency = "3"; // Monthly
final String interval = "1"; //Every One Moth
Integer calendarID = calendarHelper.createMeetingForGroup(requestSpec, responseSpec, groupID, startDate, frequency,
interval, null);
System.out.println("caladerId --------------------" + calendarID);
final Integer loanProductID = createLoanProduct();
final Integer loanID = applyForLoanApplication(groupID, loanProductID, calendarID, clientID);
System.out.println("loanID----" + loanID);
final ArrayList<HashMap> loanSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(this.requestSpec,
this.responseSpec, loanID);
verifyLoanRepaymentSchedule(loanSchedule);
}
private Integer createLoanProduct() {
System.out.println(
"------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
final String loanProductJSON = new LoanProductTestBuilder() //
.withPrincipal("12,000.00") //
.withNumberOfRepayments("4") //
.withRepaymentAfterEvery("1") //
.withRepaymentTypeAsMonth() //
.withinterestRatePerPeriod("1") //
.withInterestRateFrequencyTypeAsMonths() //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
.build(null);
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private Integer applyForLoanApplication(final Integer groupID, final Integer loanProductID, Integer calendarID,
Integer clientID) {
System.out.println(
"--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
.withPrincipal("12,000.00") //
.withLoanTermFrequency("4") //
.withLoanTermFrequencyAsMonths() //
.withNumberOfRepayments("4") //
.withRepaymentEveryAfter("1") //
.withRepaymentFrequencyTypeAsMonths() //
.withInterestRatePerPeriod("2") //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
.withExpectedDisbursementDate("01 October 2011") //
.withCalendarID(calendarID.toString()) //
.withSubmittedOnDate("01 October 2011") //
.withLoanType("jlg").build(clientID.toString(), groupID.toString(), loanProductID.toString(), null);
System.out.println(loanApplicationJSON);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
private void verifyLoanRepaymentSchedule(final ArrayList<HashMap> loanSchedule) {
System.out.println("--------------------VERIFYING THE REPAYMENT DATE--------------------------");
assertEquals("Checking for Repayment Date for 1st Month", new ArrayList<>(Arrays.asList(2011, 10, 15)),
loanSchedule.get(1).get("dueDate"));
System.out.println("Repayment Date for 1st Month--" + loanSchedule.get(1).get("dueDate"));
assertEquals("Checking for Repayment Date for 2nd Month", new ArrayList<>(Arrays.asList(2011, 11, 15)),
loanSchedule.get(2).get("dueDate"));
System.out.println("Repayment Date for 2nd Month--" + loanSchedule.get(2).get("dueDate"));
assertEquals("Checking for Repayment Date for 3rd Month", new ArrayList<>(Arrays.asList(2011, 12, 15)),
loanSchedule.get(3).get("dueDate"));
System.out.println("Repayment Date for 3rd Month--" + loanSchedule.get(3).get("dueDate"));
assertEquals("Checking for Repayment Date for 4th Month", new ArrayList<>(Arrays.asList(2012, 1, 15)),
loanSchedule.get(4).get("dueDate"));
System.out.println("Repayment Date for 4th Month--" + loanSchedule.get(4).get("dueDate"));
}
}
@@ -70,6 +70,11 @@ public interface ConfigurationDomainService {
Date retrieveOrganisationStartDate();
boolean isPaymnetypeApplicableforDisbursementCharge();
boolean isInterestChargedFromDateSameAsDisbursementDate();
boolean isSkippingMeetingOnFirstDayOfMonthEnabled();
Long retreivePeroidInNumberOfDaysForSkipMeetingDate();
}
@@ -235,6 +235,19 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
return property.isEnabled();
}
@Override
public boolean isSkippingMeetingOnFirstDayOfMonthEnabled() {
return this.globalConfigurationRepository.findOneByNameWithNotFoundDetection("skip-repayment-on-first-day-of-month").isEnabled();
}
@Override
public Long retreivePeroidInNumberOfDaysForSkipMeetingDate() {
final String propertyName = "skip-repayment-on-first-day-of-month";
final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName);
return property.getValue();
}
@Override
public boolean isInterestChargedFromDateSameAsDisbursementDate() {
@@ -334,9 +334,9 @@ public class CalendarData {
return false;
}
public boolean isValidRecurringDate(final LocalDate compareDate) {
public boolean isValidRecurringDate(final LocalDate compareDate, final Boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(this.getRecurrence(), this.getStartDate(),
compareDate); }
compareDate, isSkipMeetingOnFirstDay, numberOfDays); }
return false;
}
@@ -560,15 +560,15 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> {
return recurrenceBuilder.toString();
}
public boolean isValidRecurringDate(final LocalDate compareDate) {
public boolean isValidRecurringDate(final LocalDate compareDate, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(),
compareDate); }
compareDate, isSkipRepaymentOnFirstMonth, numberOfDays); }
// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) { return CalendarUtils.isValidRedurringDate(history.getRecurrence(),
history.getStartDateLocalDate(), compareDate); }
history.getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth, numberOfDays); }
}
return false;
@@ -142,4 +142,9 @@ public class CalendarHistory extends AbstractPersistable<Long> {
public void updateEndDate(Date historyCalEndDate) {
this.endDate = historyCalEndDate;
}
public Calendar getCalendar() {
return this.calendar;
}
}
@@ -50,4 +50,6 @@ public interface CalendarReadPlatformService {
LocalDate generateNextEligibleMeetingDateForCollection(CalendarData calendarData, MeetingData lastMeetingData);
Boolean isCalendarAssociatedWithEntity(final Long entityId, final Long calendarId, Long entityTypeId);
}
@@ -24,6 +24,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -45,10 +46,12 @@ import org.springframework.util.CollectionUtils;
public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformService {
private final JdbcTemplate jdbcTemplate;
private final ConfigurationDomainService configurationDomainService;
@Autowired
public CalendarReadPlatformServiceImpl(final RoutingDataSource dataSource) {
public CalendarReadPlatformServiceImpl(final RoutingDataSource dataSource, final ConfigurationDomainService configurationDomainService) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.configurationDomainService = configurationDomainService;
}
private static final class CalendarDataMapper implements RowMapper<CalendarData> {
@@ -237,11 +240,34 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
* till periodEndDate recurring dates will be generated.
*/
final LocalDate periodEndDate = this.getPeriodEndDate(calendarData.getEndDate(), tillDate);
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService
.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
numberOfDays = this.configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
}
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate, periodEndDate,
maxCount);
return recurringDates;
}
final Collection<LocalDate> recurringDates = CalendarUtils.getRecurringDates(rrule, seedDate, periodStartDate,
periodEndDate, maxCount, isSkipRepaymentOnFirstMonthEnabled, numberOfDays);
return recurringDates;
}
@Override
public Boolean isCalendarAssociatedWithEntity(final Long entityId, final Long calendarId, final Long entityTypeId) {
String query = "Select COUNT(*) from m_calendar_instance ci where ci.entity_id = ? and ci.calendar_id = ? and "
+ " ci.entity_type_enum = ?";
try {
int calendarInstaneId = this.jdbcTemplate.queryForObject(query,
new Object[] { entityId, calendarId, entityTypeId }, Integer.class);
if (calendarInstaneId > 0) {
return true;
}
return false;
} catch (final EmptyResultDataAccessException e) {
return false;
}
}
private LocalDate getSeedDate(LocalDate date) {
return date;
@@ -301,26 +327,37 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
* which is still on Tuesday and next collection sheet date should be on
* 18th of Oct as per current calendar
*/
if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate)
&& !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) {
applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate);
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), lastMeetingDate);
}
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService
.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
}
/**
* If nextEligibleMeetingDate is on or after current calendar startdate
* then regenerate the nextEligible meeting date based on
*/
if (nextEligibleMeetingDate == null) {
final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate();
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(), seedDate);
} else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) {
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
calendarData.getStartDate());
}
if (lastMeetingDate != null && !calendarData.isBetweenStartAndEndDate(lastMeetingDate)
&& !calendarData.isBetweenStartAndEndDate(DateUtils.getLocalDateOfTenant())) {
applicableCalendarData = this.retrieveApplicableCalendarFromHistory(calendarData.getId(), lastMeetingDate);
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
lastMeetingDate, isSkipRepaymentOnFirstMonthEnabled, numberOfDays);
}
return nextEligibleMeetingDate;
}
/**
* If nextEligibleMeetingDate is on or after current calendar startdate
* then regenerate the nextEligible meeting date based on
*/
if (nextEligibleMeetingDate == null) {
final LocalDate seedDate = (lastMeetingDate != null) ? lastMeetingDate : calendarData.getStartDate();
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
seedDate, isSkipRepaymentOnFirstMonthEnabled, numberOfDays);
} else if (calendarData.isBetweenStartAndEndDate(nextEligibleMeetingDate)) {
nextEligibleMeetingDate = CalendarUtils.getRecentEligibleMeetingDate(applicableCalendarData.getRecurrence(),
calendarData.getStartDate(), isSkipRepaymentOnFirstMonthEnabled, numberOfDays);
}
return nextEligibleMeetingDate;
}
@Override
public Collection<CalendarData> updateWithRecurringDates(final Collection<CalendarData> calendarsData) {
@@ -465,4 +502,6 @@ public class CalendarReadPlatformServiceImpl implements CalendarReadPlatformServ
lastUpdatedByUserName);
}
}
}
@@ -133,19 +133,23 @@ public class CalendarUtils {
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
final LocalDate periodStartDate, final LocalDate periodEndDate) {
final int maxCount = 10;// Default number of recurring dates
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount);
boolean isSkipRepaymentOnFirstdayofMonth = false;
final Integer numberofDays = 0;
return getRecurringDates(recurringRule, seedDate, periodStartDate, periodEndDate, maxCount, isSkipRepaymentOnFirstdayofMonth,
numberofDays);
}
public static Collection<LocalDate> getRecurringDates(final String recurringRule, final LocalDate seedDate,
final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount) {
final LocalDate periodStartDate, final LocalDate periodEndDate, final int maxCount, boolean isSkippMeetingOnFirstDay,
final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount);
return getRecurringDates(recur, seedDate, periodStartDate, periodEndDate, maxCount, isSkippMeetingOnFirstDay, numberOfDays);
}
private static Collection<LocalDate> getRecurringDates(final Recur recur, final LocalDate seedDate, final LocalDate periodStartDate,
final LocalDate periodEndDate, final int maxCount) {
final LocalDate periodEndDate, final int maxCount, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) {
if (recur == null) { return null; }
final Date seed = convertToiCal4JCompatibleDate(seedDate);
final DateTime periodStart = new DateTime(periodStartDate.toDate());
@@ -153,11 +157,12 @@ public class CalendarUtils {
final Value value = new Value(Value.DATE.getValue());
final DateList recurringDates = recur.getDates(seed, periodStart, periodEnd, value, maxCount);
return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur));
return convertToLocalDateList(recurringDates, seedDate, getMeetingPeriodFrequencyType(recur), isSkippMeetingOnFirstDay,
numberOfDays);
}
private static Collection<LocalDate> convertToLocalDateList(final DateList dates, final LocalDate seedDate,
final PeriodFrequencyType frequencyType) {
final PeriodFrequencyType frequencyType, boolean isSkippMeetingOnFirstDay, final Integer numberOfDays) {
final Collection<LocalDate> recurringDates = new ArrayList<>();
@@ -167,9 +172,30 @@ public class CalendarUtils {
recurringDates.add(adjustDate(new LocalDate(date), seedDate, frequencyType));
}
if (isSkippMeetingOnFirstDay) { return skipMeetingOnFirstdayOfMonth(recurringDates, numberOfDays); }
return recurringDates;
}
private static Collection<LocalDate> skipMeetingOnFirstdayOfMonth(final Collection<LocalDate> recurringDates, final Integer numberOfDays) {
final Collection<LocalDate> adjustedRecurringDates = new ArrayList<>();
for (@SuppressWarnings("rawtypes")
final Iterator iterator = recurringDates.iterator(); iterator.hasNext();) {
LocalDate recuringDate = (LocalDate) iterator.next();
adjustedRecurringDates.add(adjustRecurringDate(recuringDate, numberOfDays));
}
return adjustedRecurringDates;
}
public static LocalDate adjustRecurringDate(final LocalDate recuringDate, final Integer numberOfDays) {
if (recuringDate.getDayOfMonth() == 1) {
LocalDate adjustedRecurringDate = recuringDate.plusDays(numberOfDays);
return adjustedRecurringDate;
}
return recuringDate;
}
public static Recur getICalRecur(final String recurringRule) {
// Construct RRule
@@ -269,14 +295,30 @@ public class CalendarUtils {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return false; }
return isValidRecurringDate(recur, seedDate, date);
final boolean isSkipRepaymentonFirstDayOfMonth = false;
final int numberOfDays = 0;
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}
public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date) {
public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final Integer numberOfDays) {
final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, date, date.plusDays(1), 1);
return (recurDate == null || recurDate.isEmpty()) ? false : true;
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return false; }
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}
public static boolean isValidRecurringDate(final Recur recur, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final int numberOfDays) {
LocalDate startDate = date;
if (isSkipRepaymentonFirstDayOfMonth && date.getDayOfMonth() == (numberOfDays + 1)) {
startDate = startDate.minusDays(numberOfDays);
}
final Collection<LocalDate> recurDate = getRecurringDates(recur, seedDate, startDate, date.plusDays(1), 1,
isSkipRepaymentonFirstDayOfMonth, numberOfDays);
return (recurDate == null || recurDate.isEmpty()) ? false : recurDate.contains(date);
}
public static enum DayNameEnum {
@@ -359,12 +401,13 @@ public class CalendarUtils {
}
public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate,
final Integer loanRepaymentInterval, final String frequency) {
final Integer loanRepaymentInterval, final String frequency, boolean isSkipRepaymentOnFirstDayOfMonth,
final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(calendar.getRecurrence());
if (recur == null) { return null; }
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate)) {
if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
startDate = startDate.plusDays(1);
}
// Recurring dates should follow loanRepaymentInterval.
@@ -386,25 +429,32 @@ public class CalendarUtils {
}
final LocalDate firstRepaymentDate = getNextRecurringDate(recur, seedDate, startDate);
if (isSkipRepaymentOnFirstDayOfMonth && firstRepaymentDate.getDayOfMonth() == 1) { return adjustRecurringDate(firstRepaymentDate,
numberOfDays); }
return firstRepaymentDate;
}
public static LocalDate getNewRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays) {
final LocalDate oldRepaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
final boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
if (isValidRecurringDate(recur, seedDate, oldRepaymentDate)) { return oldRepaymentDate; }
return getNextRepaymentMeetingDate(recurringRule, seedDate, oldRepaymentDate, loanRepaymentInterval, frequency, workingDays);
if (isValidRecurringDate(recur, seedDate, oldRepaymentDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) { return oldRepaymentDate; }
LocalDate nextRapaymentDate = getNextRepaymentMeetingDate(recurringRule, seedDate, oldRepaymentDate, loanRepaymentInterval,
frequency, workingDays, isSkipRepaymentOnFirstDayOfMonth, numberOfDays);
return nextRapaymentDate;
}
public static LocalDate getNextRepaymentMeetingDate(final String recurringRule, final LocalDate seedDate,
final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays) {
final LocalDate repaymentDate, final Integer loanRepaymentInterval, final String frequency, final WorkingDays workingDays,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
LocalDate tmpDate = repaymentDate;
if (isValidRecurringDate(recur, seedDate, repaymentDate)) {
if (isValidRecurringDate(recur, seedDate, repaymentDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
tmpDate = repaymentDate.plusDays(1);
}
/*
@@ -430,6 +480,10 @@ public class CalendarUtils {
final LocalDate nextRepaymentDate = getNextRecurringDate(recur, seedDate, newRepaymentDate);
newRepaymentDate = WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDate, nextRepaymentDate, workingDays);
if (isSkipRepaymentOnFirstDayOfMonth) {
LocalDate newRepaymentDateTemp = adjustRecurringDate(newRepaymentDate, numberOfDays);
return WorkingDaysUtil.getOffSetDateIfNonWorkingDay(newRepaymentDateTemp, nextRepaymentDate, workingDays);
}
return newRepaymentDate;
}
@@ -500,12 +554,13 @@ public class CalendarUtils {
return sqlCalendarTypeOptions;
}
public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate) {
public static LocalDate getRecentEligibleMeetingDate(final String recurringRule, final LocalDate seedDate,
final boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
LocalDate currentDate = DateUtils.getLocalDateOfTenant();
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) { return null; }
if (isValidRecurringDate(recur, seedDate, currentDate)) { return currentDate; }
if (isValidRecurringDate(recur, seedDate, currentDate, isSkipMeetingOnFirstDay, numberOfDays)) { return currentDate; }
if (recur.getFrequency().equals(Recur.DAILY)) {
currentDate = currentDate.plusDays(recur.getInterval());
@@ -36,6 +36,7 @@ import java.util.Set;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
@@ -44,8 +45,10 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.calendar.domain.CalendarRepositoryWrapper;
import org.apache.fineract.portfolio.calendar.exception.NotValidRecurringDateException;
import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
import org.apache.fineract.portfolio.collectionsheet.data.IndividualClientData;
import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetData;
import org.apache.fineract.portfolio.collectionsheet.data.IndividualCollectionSheetLoanFlatData;
@@ -91,6 +94,9 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
private final MandatorySavingsCollectionsheetExtractor mandatorySavingsExtractor = new MandatorySavingsCollectionsheetExtractor();
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final PaymentTypeReadPlatformService paymentTypeReadPlatformService;
private final CalendarReadPlatformService calendarReadPlatformService;
private final ConfigurationDomainService configurationDomainService;
private final CalendarInstanceRepository calendarInstanceRepository;
@Autowired
public CollectionSheetReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
@@ -98,7 +104,9 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final CollectionSheetGenerateCommandFromApiJsonDeserializer collectionSheetGenerateCommandFromApiJsonDeserializer,
final CalendarRepositoryWrapper calendarRepositoryWrapper,
final AttendanceDropdownReadPlatformService attendanceDropdownReadPlatformService,
final CodeValueReadPlatformService codeValueReadPlatformService, final PaymentTypeReadPlatformService paymentTypeReadPlatformService) {
final CodeValueReadPlatformService codeValueReadPlatformService, final PaymentTypeReadPlatformService paymentTypeReadPlatformService,
final CalendarReadPlatformService calendarReadPlatformService, final ConfigurationDomainService configurationDomainService,
final CalendarInstanceRepository calendarInstanceRepository) {
this.context = context;
this.centerReadPlatformService = centerReadPlatformService;
this.namedParameterjdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
@@ -108,6 +116,9 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
this.attendanceDropdownReadPlatformService = attendanceDropdownReadPlatformService;
this.codeValueReadPlatformService = codeValueReadPlatformService;
this.paymentTypeReadPlatformService = paymentTypeReadPlatformService;
this.calendarReadPlatformService = calendarReadPlatformService;
this.configurationDomainService = configurationDomainService;
this.calendarInstanceRepository = calendarInstanceRepository;
}
/*
@@ -320,20 +331,39 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe
final Calendar calendar = this.calendarRepositoryWrapper.findOneWithNotFoundDetection(calendarId);
// check if transaction against calendar effective from date
final GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId);
// entityType should be center if it's within a center
final CalendarEntityType entityType = (group.isChildGroup()) ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS;
Long entityId = null;
if(group.isChildGroup()){
entityId = group.getParentId();
}else{
entityId = group.getId();
}
if (!calendar.isValidRecurringDate(transactionDate)) { throw new NotValidRecurringDateException("collectionsheet", "The date '"
Boolean isSkipMeetingOnFirstDay = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
numberOfDays = this.configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
isSkipMeetingOnFirstDay = this.calendarReadPlatformService.isCalendarAssociatedWithEntity(entityId, calendar.getId(),
entityType.getValue().longValue());
}
if (!calendar.isValidRecurringDate(transactionDate, isSkipMeetingOnFirstDay, numberOfDays)) { throw new NotValidRecurringDateException("collectionsheet", "The date '"
+ transactionDate + "' is not a valid meeting date.", transactionDate); }
final AppUser currentUser = this.context.authenticatedUser();
final String hierarchy = currentUser.getOffice().getHierarchy();
final String officeHierarchy = hierarchy + "%";
final GroupGeneralData group = this.groupReadPlatformService.retrieveOne(groupId);
final JLGCollectionSheetFaltDataMapper mapper = new JLGCollectionSheetFaltDataMapper();
// entityType should be center if it's within a center
final CalendarEntityType entityType = (group.isChildGroup()) ? CalendarEntityType.CENTERS : CalendarEntityType.GROUPS;
final SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("dueDate", transactionDateStr)
.addValue("groupId", group.getId()).addValue("officeHierarchy", officeHierarchy)
@@ -31,6 +31,7 @@ import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -50,6 +51,7 @@ import org.apache.fineract.organisation.staff.data.StaffData;
import org.apache.fineract.organisation.staff.service.StaffReadPlatformService;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations;
import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
import org.apache.fineract.portfolio.client.data.ClientData;
import org.apache.fineract.portfolio.client.domain.ClientEnumerations;
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
@@ -79,6 +81,8 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
private final OfficeReadPlatformService officeReadPlatformService;
private final StaffReadPlatformService staffReadPlatformService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final ConfigurationDomainService configurationDomainService;
private final CalendarReadPlatformService calendarReadPlatformService;
// data mappers
private final CenterDataMapper centerMapper = new CenterDataMapper();
@@ -92,7 +96,8 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
public CenterReadPlatformServiceImpl(final PlatformSecurityContext context, final RoutingDataSource dataSource,
final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService,
final StaffReadPlatformService staffReadPlatformService, final CodeValueReadPlatformService codeValueReadPlatformService,
final PaginationParametersDataValidator paginationParametersDataValidator) {
final PaginationParametersDataValidator paginationParametersDataValidator, final ConfigurationDomainService configurationDomainService,
final CalendarReadPlatformService calendarReadPlatformService) {
this.context = context;
this.clientReadPlatformService = clientReadPlatformService;
this.jdbcTemplate = new JdbcTemplate(dataSource);
@@ -100,6 +105,8 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
this.staffReadPlatformService = staffReadPlatformService;
this.codeValueReadPlatformService = codeValueReadPlatformService;
this.paginationParametersDataValidator = paginationParametersDataValidator;
this.configurationDomainService = configurationDomainService;
this.calendarReadPlatformService = calendarReadPlatformService;
}
// 'g.' preffix because of ERROR 1052 (23000): Column 'column_name' in where
@@ -521,8 +528,13 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService
Collection<StaffCenterData> staffCenterDataArray = new ArrayList<>();
Boolean flag = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
numberOfDays = this.configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
}
for (CenterData centerData : centerDataArray) {
if (centerData.getCollectionMeetingCalendar().isValidRecurringDate(new LocalDate(meetingDate))) {
if (centerData.getCollectionMeetingCalendar().isValidRecurringDate(new LocalDate(meetingDate), isSkipRepaymentOnFirstMonthEnabled, numberOfDays)) {
if (staffCenterDataArray.size() <= 0) {
Collection<CenterData> meetingFallCenter = new ArrayList<>();
meetingFallCenter.add(centerData);
@@ -40,14 +40,18 @@ public class ScheduleGeneratorDTO {
final Calendar calendar;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper;
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled;
final Integer numberOfdays;
final boolean isSkipRepaymentOnFirstDayofMonth;
public ScheduleGeneratorDTO(final LoanScheduleGeneratorFactory loanScheduleFactory, final ApplicationCurrency applicationCurrency,
final LocalDate calculatedRepaymentsStartingFromDate, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance calendarInstanceForInterestRecalculation, final CalendarInstance compoundingCalendarInstance,
final LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO,
final Calendar calendar, final CalendarHistoryDataWrapper calendarHistoryDataWrapper,
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled) {
final Calendar calendar, final CalendarHistoryDataWrapper calendarHistoryDataWrapper,
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled, final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth) {
this.loanScheduleFactory = loanScheduleFactory;
this.applicationCurrency = applicationCurrency;
this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate;
@@ -61,6 +65,8 @@ public class ScheduleGeneratorDTO {
this.calendarHistoryDataWrapper = calendarHistoryDataWrapper;
this.isInterestChargedFromDateAsDisbursementDateEnabled = isInterestChargedFromDateAsDisbursementDateEnabled;
this.numberOfdays = numberOfdays;
this.isSkipRepaymentOnFirstDayofMonth = isSkipRepaymentOnFirstDayofMonth;
}
public LoanScheduleGeneratorFactory getLoanScheduleFactory() {
@@ -123,4 +129,12 @@ public class ScheduleGeneratorDTO {
return this.isInterestChargedFromDateAsDisbursementDateEnabled;
}
public Integer getNumberOfdays() {
return numberOfdays;
}
public boolean isSkipRepaymentOnFirstDayofMonth() {
return isSkipRepaymentOnFirstDayofMonth;
}
}
@@ -4170,7 +4170,8 @@ public class Loan extends AbstractPersistable<Long> {
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate) {
final Boolean reschedulebasedOnMeetingDates, final LocalDate presentMeetingDate, final LocalDate newMeetingDate,
final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
// first repayment's from date is same as disbursement date.
/*
@@ -4202,7 +4203,7 @@ public class Loan extends AbstractPersistable<Long> {
// getNewRepaymentMeetingDate method returns next meeting
// date and not the same as tmpFromDate
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, tmpFromDate, tmpFromDate.plusDays(1),
loanRepaymentInterval, frequency, workingDays);
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
}
if (isHolidayEnabled) {
@@ -4230,7 +4231,8 @@ public class Loan extends AbstractPersistable<Long> {
}
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule,
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) {
final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays,
final boolean isSkipRepaymentonfirstdayofmonth, final Integer numberofDays) {
// first repayment's from date is same as disbursement date.
LocalDate tmpFromDate = getDisbursementDate();
@@ -4250,14 +4252,14 @@ public class Loan extends AbstractPersistable<Long> {
if (oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) {
newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval,
frequency, workingDays);
frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment(repaymentPeriodFrequencyType,
loanRepaymentInterval, tmpFromDate);
if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) {
newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate,
loanRepaymentInterval, frequency, workingDays);
loanRepaymentInterval, frequency, workingDays, isSkipRepaymentonfirstdayofmonth, numberofDays);
}
if (isHolidayEnabled) {
@@ -5046,9 +5048,9 @@ public class Loan extends AbstractPersistable<Long> {
compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod();
compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType();
rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod();
calendar = scheduleGeneratorDTO.getCalendar();
calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper();
}
calendar = scheduleGeneratorDTO.getCalendar();
calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper();
BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate();
FloatingRateDTO floatingRateDTO = scheduleGeneratorDTO.getFloatingRateDTO();
@@ -5066,7 +5068,8 @@ public class Loan extends AbstractPersistable<Long> {
this.maxOutstandingLoanBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(),
this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper);
rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper,
scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth());
return loanApplicationTerms;
}
@@ -5230,7 +5233,7 @@ public class Loan extends AbstractPersistable<Long> {
@SuppressWarnings({ "unused" })
public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency,
final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar,
final FloatingRateDTO floatingRateDTO) {
final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) {
LoanProduct loanProduct = loanProduct();
// LoanProductRelatedDetail loanProductRelatedDetail =
// getLoanRepaymentScheduleDetail();
@@ -5310,7 +5313,7 @@ public class Loan extends AbstractPersistable<Long> {
this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod,
compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(),
rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper);
calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst);
}
/**
@@ -57,6 +57,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.Schedule
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.joda.time.Days;
import org.joda.time.LocalDate;
@@ -1581,7 +1582,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO,
final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar,
final FloatingRateDTO floatingRateDTO) {
final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) {
final Loan loan = loanRescheduleRequest.getLoan();
final LoanSummary loanSummary = loan.getSummary();
@@ -1723,7 +1724,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
// get the loan application terms from the Loan object
final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance,
compoundingCalendarInstance, loanCalendar, floatingRateDTO);
compoundingCalendarInstance, loanCalendar, floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
// for applying variations
Collection<LoanTermVariationsData> loanTermVariations = loanApplicationTerms.getLoanTermVariations().getInterestRateChanges();
@@ -92,9 +92,11 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1),
loanApplicationTerms.getRepaymentEvery(),
CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()),
holidayDetailDTO.getWorkingDays());
holidayDetailDTO.getWorkingDays(), loanApplicationTerms.isSkipRepaymentOnFirstDayofMonth(),
loanApplicationTerms.getNumberOfdays());
}
}
return dueRepaymentPeriodDate;
}
@@ -183,6 +183,10 @@ public final class LoanApplicationTerms {
private final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled;
private final Integer numberOfDays;
private final boolean isSkipRepaymentOnFirstDayOfMonth;
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType,
@@ -199,9 +203,11 @@ public final class LoanApplicationTerms {
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled) {
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled,
final Integer numberOfdays,boolean isSkipRepaymentOnFirstDayofMonth) {
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null;
final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery,
@@ -214,7 +220,8 @@ public final class LoanApplicationTerms {
interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled);
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -229,7 +236,7 @@ public final class LoanApplicationTerms {
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate,
List<LoanTermVariationsData> loanTermVariations) {
List<LoanTermVariationsData> loanTermVariations, final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth) {
final Calendar loanCalendar = null;
final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null;
@@ -239,7 +246,7 @@ public final class LoanApplicationTerms {
principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance,
compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy,
rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations,
calendarHistoryDataWrapper);
calendarHistoryDataWrapper, numberOfdays, isSkipRepaymentOnFirstDayofMonth);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -255,7 +262,8 @@ public final class LoanApplicationTerms {
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy,
final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount,
BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations,
final CalendarHistoryDataWrapper calendarHistoryDataWrapper) {
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfdays,
final boolean isSkipRepaymentOnFirstDayofMonth) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -289,7 +297,7 @@ public final class LoanApplicationTerms {
rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance,
compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled);
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth);
}
public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency,
@@ -302,7 +310,8 @@ public final class LoanApplicationTerms {
final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType,
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) {
BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations,
Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) {
final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments();
final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery();
@@ -344,7 +353,8 @@ public final class LoanApplicationTerms {
rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType,
compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf,
loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper,
isInterestChargedFromDateSameAsDisbursalDateEnabled);
isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth);
}
public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms,
@@ -367,8 +377,9 @@ public final class LoanApplicationTerms {
applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf,
applicationTerms.preClosureInterestCalculationStrategy, applicationTerms.loanCalendar,
applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, applicationTerms.calendarHistoryDataWrapper,
applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled);
}
applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, applicationTerms.numberOfDays,
applicationTerms.isSkipRepaymentOnFirstDayOfMonth);
}
private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency,
final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery,
@@ -389,7 +400,9 @@ public final class LoanApplicationTerms {
final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf,
final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar,
BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations,
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled) {
final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled,
final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth) {
this.currency = currency;
this.loanTermFrequency = loanTermFrequency;
this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType;
@@ -436,6 +449,8 @@ public final class LoanApplicationTerms {
this.principalThresholdForLastInstalment = principalThresholdForLastInstalment;
this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf;
this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy;
this.isSkipRepaymentOnFirstDayOfMonth = isSkipRepaymentOnFirstDayofMonth;
this.numberOfDays = numberOfdays;
this.loanCalendar = loanCalendar;
this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount);
@@ -1454,4 +1469,12 @@ public final class LoanApplicationTerms {
return this.isInterestChargedFromDateSameAsDisbursalDateEnabled;
}
public Integer getNumberOfdays() {
return numberOfDays;
}
public boolean isSkipRepaymentOnFirstDayofMonth() {
return isSkipRepaymentOnFirstDayOfMonth;
}
}
@@ -54,5 +54,6 @@ public interface LoanScheduleGenerator {
LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest,
final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance,
CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO);
CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays);
}
@@ -270,14 +270,29 @@ public class LoanScheduleAssembler {
* If it is JLG loan/Group Loan synched with a meeting, then make sure
* first repayment falls on meeting date
*/
final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
Group group = null;
if(groupId != null){
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
Boolean isSkipMeetingOnFirstDay = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipMeetingOnFirstDay = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(group, calendar);
if(isSkipMeetingOnFirstDay) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) {
validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar);
validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar, isSkipMeetingOnFirstDay,
numberOfDays);
/*
* If disbursement is synced on meeting, make sure disbursement date
* is on a meeting date
*/
if (synchDisbursement != null && synchDisbursement.booleanValue()) {
validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar, isSkipMeetingOnFirstDay, numberOfDays);
}
}
@@ -397,8 +412,8 @@ public class LoanScheduleAssembler {
maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled,
recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType,
principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(),
calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled);
}
calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled,numberOfDays, isSkipMeetingOnFirstDay);
}
private CalendarInstance createCalendarForSameAsRepayment(final Integer repaymentEvery,
final PeriodFrequencyType repaymentPeriodFrequencyType, final LocalDate expectedDisbursementDate) {
@@ -468,19 +483,20 @@ public class LoanScheduleAssembler {
return disbursementDatas;
}
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar) {
private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
if (repaymentsStartingFromDate != null
&& !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(),
repaymentsStartingFromDate)) {
repaymentsStartingFromDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
repaymentsStartingFromDate);
}
}
public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar) {
public void validateDisbursementDateWithMeetingDates(final LocalDate expectedDisbursementDate, final Calendar calendar, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
// disbursement date should fall on a meeting date
if (!calendar.isValidRecurringDate(expectedDisbursementDate)) {
if (!calendar.isValidRecurringDate(expectedDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
final String errorMessage = "Expected disbursement date '" + expectedDisbursementDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("disbursement.date.do.not.match.meeting.date", errorMessage, expectedDisbursementDate);
}
@@ -747,6 +763,13 @@ public class LoanScheduleAssembler {
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
final Integer minGap = installmentConfig.getMinimumGap();
final Integer maxGap = installmentConfig.getMaximumGap();
@@ -760,7 +783,8 @@ public class LoanScheduleAssembler {
.value(duedate)
.failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.must.be.in.min.max.range",
"Loan schedule date invalid");
} else if (loanCalendar != null && !actualDueDates.contains(duedate) && !loanCalendar.isValidRecurringDate(duedate)) {
} else if (loanCalendar != null && !actualDueDates.contains(duedate) && !loanCalendar.isValidRecurringDate(duedate,
isSkipRepaymentOnFirstMonth, numberOfDays)) {
baseDataValidator
.reset()
.value(duedate)
@@ -1004,10 +1028,11 @@ public class LoanScheduleAssembler {
private LocalDate deriveFirstRepaymentDateForJLGLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate,
final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType,
final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) {
boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
int numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType);
final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar,
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency);
refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays);
final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate.plusDays(minimumDaysBetweenDisbursalAndFirstRepayment);
return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForJLGLoans(
repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType,
@@ -35,7 +35,8 @@ public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO) {
final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, final FloatingRateDTO floatingRateDTO,
final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) {
LoanRescheduleModel loanRescheduleModel = null;
@@ -43,13 +44,13 @@ public class DefaultLoanReschedulerFactory implements LoanReschedulerFactory {
case DECLINING_BALANCE:
loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO);
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case FLAT:
loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest,
applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar,
floatingRateDTO);
floatingRateDTO, isSkipRepaymentonmonthFirst, numberofdays);
break;
case INVALID:
@@ -32,5 +32,6 @@ public interface LoanReschedulerFactory {
public LoanRescheduleModel reschedule(final MathContext mathContext, final InterestMethod interestMethod,
final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance,
final Calendar loanCalendar, FloatingRateDTO floatingRateDTO);
final Calendar loanCalendar, FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst,
final Integer numberofdays);
}
@@ -50,6 +50,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
import org.springframework.beans.factory.annotation.Autowired;
@@ -66,6 +67,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final CalendarInstanceRepository calendarInstanceRepository;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final LoanUtilService loanUtilService;
@Autowired
public LoanReschedulePreviewPlatformServiceImpl(final LoanRescheduleRequestRepository loanRescheduleRequestRepository,
@@ -74,7 +76,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
final CalendarInstanceRepository calendarInstanceRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService) {
final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService) {
this.loanRescheduleRequestRepository = loanRescheduleRequestRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.configurationDomainService = configurationDomainService;
@@ -83,6 +85,7 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
this.loanScheduleHistoryWritePlatformService = loanScheduleHistoryWritePlatformService;
this.calendarInstanceRepository = calendarInstanceRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.loanUtilService = loanUtilService;
}
@Override
@@ -122,9 +125,17 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP
loanCalendar = loanCalendarInstance.getCalendar();
}
final FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar, floatingRateDTO);
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel,
oldPeriods);
return loanRescheduleModelWithOldPeriods;
@@ -355,9 +355,17 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
loanCalendar = loanCalendarInstance.getCalendar();
}
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod,
loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance,
loanCalendar, floatingRateDTO);
loanCalendar, floatingRateDTO, isSkipRepaymentOnFirstMonth, numberOfDays);
final Collection<LoanRescheduleModelRepaymentPeriod> periods = loanRescheduleModel.getPeriods();
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments();
@@ -109,10 +109,11 @@ public final class LoanEventApiJsonValidator {
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance) {
public void validateDisbursementDateWithMeetingDate(final LocalDate actualDisbursementDate, final CalendarInstance calendarInstance,
Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {
if (null != calendarInstance) {
final Calendar calendar = calendarInstance.getCalendar();
if (!calendar.isValidRecurringDate(actualDisbursementDate)) {
if (!calendar.isValidRecurringDate(actualDisbursementDate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
// Disbursement date should fall on a meeting date
final String errorMessage = "Expected disbursement date '" + actualDisbursementDate.toString()
+ "' does not fall on a meeting date.";
@@ -889,13 +889,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
checkClientOrGroupActive(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
// validate expected disbursement date against meeting date
if (loan.isSyncDisbursementWithMeeting() && (loan.isGroupLoan() || loan.isJLGLoan())) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
final Calendar calendar = calendarInstance.getCalendar();
this.loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar);
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
this.loanScheduleAssembler.validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar,
isSkipRepaymentOnFirstMonth, numberOfDays);
}
final Map<String, Object> changes = loan.loanApplicationApproval(currentUser, command, disbursementDataArray,
@@ -42,11 +42,13 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO;
import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
@@ -72,13 +74,14 @@ public class LoanUtilService {
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final CalendarReadPlatformService calendarReadPlatformService;
@Autowired
public LoanUtilService(final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final CalendarInstanceRepository calendarInstanceRepository, final ConfigurationDomainService configurationDomainService,
final HolidayRepository holidayRepository, final WorkingDaysRepositoryWrapper workingDaysRepository,
final LoanScheduleGeneratorFactory loanScheduleFactory, final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final FromJsonHelper fromApiJsonHelper) {
final FromJsonHelper fromApiJsonHelper, final CalendarReadPlatformService calendarReadPlatformService) {
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.calendarInstanceRepository = calendarInstanceRepository;
this.configurationDomainService = configurationDomainService;
@@ -87,6 +90,7 @@ public class LoanUtilService {
this.loanScheduleFactory = loanScheduleFactory;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.fromApiJsonHelper = fromApiJsonHelper;
this.calendarReadPlatformService = calendarReadPlatformService;
}
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
@@ -125,13 +129,46 @@ public class LoanUtilService {
}
final Boolean isInterestChargedFromDateAsDisbursementDateEnabled = this.configurationDomainService.isInterestChargedFromDateSameAsDisbursementDate();
FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency,
calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom,
overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper, isInterestChargedFromDateAsDisbursementDateEnabled);
overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper, isInterestChargedFromDateAsDisbursementDateEnabled,
numberOfDays, isSkipRepaymentOnFirstMonth);
return scheduleGeneratorDTO;
return scheduleGeneratorDTO;
}
public Boolean isLoanRepaymentsSyncWithMeeting(final Group group, final Calendar calendar) {
Boolean isSkipRepaymentOnFirstMonth = false;
Long entityId = null;
Long entityTypeId = null;
if (group != null) {
if (group.getParent() != null) {
entityId = group.getParent().getId();
entityTypeId = CalendarEntityType.CENTERS.getValue().longValue();
} else {
entityId = group.getId();
entityTypeId = CalendarEntityType.GROUPS.getValue().longValue();
}
}
if (entityId == null || calendar == null) {
return isSkipRepaymentOnFirstMonth;
}
isSkipRepaymentOnFirstMonth = this.calendarReadPlatformService
.isCalendarAssociatedWithEntity(entityId, calendar.getId(), entityTypeId);
return isSkipRepaymentOnFirstMonth;
}
public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) {
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
@@ -209,8 +246,15 @@ public class LoanUtilService {
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
}
calculatedRepaymentsStartingFromDate = CalendarUtils.getFirstRepaymentMeetingDate(calendar, actualDisbursementDate,
repayEvery, frequency);
repayEvery, frequency, isSkipRepaymentOnFirstMonth, numberOfDays);
}
}
}
@@ -230,8 +274,15 @@ public class LoanUtilService {
final Integer repayEvery = repaymentScheduleDetails.getRepayEvery();
final String frequency = CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(repaymentScheduleDetails
.getRepaymentPeriodFrequencyType());
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = this.configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan.group(), historyList.get(0).getCalendar());
}
calculatedRepaymentsStartingFromDate = CalendarUtils.getNextRepaymentMeetingDate(historyList.get(0).getRecurrence(), historyList.get(0).getStartDateLocalDate(),
actualDisbursementDate, repayEvery, frequency, workingDays);
actualDisbursementDate, repayEvery, frequency, workingDays, isSkipRepaymentOnFirstMonth, numberOfDays);
}
}
return calculatedRepaymentsStartingFromDate;
@@ -310,14 +310,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// check for product mix validations
checkForProductMixRestrictions(loan);
LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
// validate actual disbursement date against meeting date
final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
if (loan.isSyncDisbursementWithMeeting()) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance);
this.loanEventApiJsonValidator.validateDisbursementDateWithMeetingDate(actualDisbursementDate, calendarInstance,
scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), scheduleGeneratorDTO.getNumberOfdays());
}
this.businessEventNotifierService.notifyBusinessEventToBeExecuted(BUSINESS_EVENTS.LOAN_DISBURSAL,
@@ -356,9 +359,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.getLoanTransactions().add(disbursementTransaction);
}
LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
regenerateScheduleOnDisbursement(command, loan, recalculateSchedule, scheduleGeneratorDTO, nextPossibleRepaymentDate, rescheduledRepaymentDate);
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive(loan.fetchRepaymentScheduleInstallments(),
@@ -1988,11 +1988,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
// loop through each loan to reschedule the repayment dates
for (final Loan loan : loans) {
if (loan != null) {
if(loan.getExpectedFirstRepaymentOnDate() != null && loan.getExpectedFirstRepaymentOnDate().equals(presentMeetingDate)){
final String defaultUserMessage = "Meeting calendar date update is not supported since its a first repayment date";
throw new CalendarParameterUpdateNotSupportedException("meeting.for.first.repayment.date", defaultUserMessage,
loan.getExpectedFirstRepaymentOnDate(), presentMeetingDate);
}
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate());
if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
@@ -2003,10 +2014,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
loan.fetchRepaymentScheduleInstallments(), loan, null);
} else if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) {
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate);
holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate,
isSkipRepaymentOnFirstMonth, numberOfDays);
} else {
loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled,
holidays, workingDays);
holidays, workingDays, isSkipRepaymentOnFirstMonth, numberOfDays);
}
saveLoanWithDataIntegrityViolationChecks(loan);
@@ -77,9 +77,11 @@ public class Meeting extends AbstractPersistable<Long> {
this.meetingDate = meetingDate;
}
public static Meeting createNew(final CalendarInstance calendarInstance, final Date meetingDate, Boolean isTransactionDateOnNonMeetingDate) {
public static Meeting createNew(final CalendarInstance calendarInstance, final Date meetingDate, Boolean isTransactionDateOnNonMeetingDate,
final boolean isSkipRepaymentOnFirstMonth, final int numberOfDays) {
if (!isTransactionDateOnNonMeetingDate && !isValidMeetingDate(calendarInstance, meetingDate)) { throw new NotValidRecurringDateException("meeting", "The date '"
if (!isTransactionDateOnNonMeetingDate && !isValidMeetingDate(calendarInstance, meetingDate,isSkipRepaymentOnFirstMonth, numberOfDays))
{ throw new NotValidRecurringDateException("meeting", "The date '"
+ meetingDate + "' is not a valid meeting date.", meetingDate); }
return new Meeting(calendarInstance, meetingDate);
}
@@ -93,7 +95,7 @@ public class Meeting extends AbstractPersistable<Long> {
this.clientsAttendance = new HashSet<>(clientsAttendance);
}
public Map<String, Object> update(final JsonCommand command) {
public Map<String, Object> update(final JsonCommand command, final boolean isSkipRepaymentOnFirstMonth, final int numberOfDays) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(9);
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
@@ -106,7 +108,7 @@ public class Meeting extends AbstractPersistable<Long> {
actualChanges.put("locale", localeAsInput);
this.meetingDate = newValue.toDate();
if (!isValidMeetingDate(this.calendarInstance, this.meetingDate)) { throw new NotValidRecurringDateException("meeting",
if (!isValidMeetingDate(this.calendarInstance, this.meetingDate, isSkipRepaymentOnFirstMonth, numberOfDays)) { throw new NotValidRecurringDateException("meeting",
"Not a valid meeting date", this.meetingDate); }
}
@@ -177,14 +179,15 @@ public class Meeting extends AbstractPersistable<Long> {
return this.meetingDate != null && newStartDate != null && getMeetingDateLocalDate().isBefore(newStartDate) ? true : false;
}
private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final Date meetingDate) {
private static boolean isValidMeetingDate(final CalendarInstance calendarInstance, final Date meetingDate,
final boolean isSkipRepaymentOnFirstMonth, final int numberOfDays) {
final Calendar calendar = calendarInstance.getCalendar();
LocalDate meetingDateLocalDate = null;
if (meetingDate != null) {
meetingDateLocalDate = LocalDate.fromDateFields(meetingDate);
}
if (meetingDateLocalDate == null || !calendar.isValidRecurringDate(meetingDateLocalDate)) { return false; }
if (meetingDateLocalDate == null || !calendar.isValidRecurringDate(meetingDateLocalDate, isSkipRepaymentOnFirstMonth, numberOfDays)) { return false; }
return true;
}
@@ -31,11 +31,13 @@ import java.util.Collection;
import java.util.Date;
import java.util.Map;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.calendar.data.CalendarData;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType;
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
@@ -43,6 +45,7 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository;
import org.apache.fineract.portfolio.calendar.domain.CalendarRepository;
import org.apache.fineract.portfolio.calendar.exception.CalendarInstanceNotFoundException;
import org.apache.fineract.portfolio.calendar.exception.CalendarNotFoundException;
import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepository;
import org.apache.fineract.portfolio.group.domain.Group;
@@ -73,12 +76,15 @@ public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWrit
private final ClientRepository clientRepository;
private final GroupRepository groupRepository;
private final FromJsonHelper fromApiJsonHelper;
private final ConfigurationDomainService configurationDomainService;
private final CalendarReadPlatformService calendarReadPlatformService;
@Autowired
public MeetingWritePlatformServiceJpaRepositoryImpl(final MeetingRepositoryWrapper meetingRepositoryWrapper,
final MeetingRepository meetingRepository, final MeetingDataValidator meetingDataValidator,
final CalendarInstanceRepository calendarInstanceRepository, final CalendarRepository calendarRepository,
final ClientRepository clientRepository, final GroupRepository groupRepository, final FromJsonHelper fromApiJsonHelper) {
final ClientRepository clientRepository, final GroupRepository groupRepository, final FromJsonHelper fromApiJsonHelper,
final ConfigurationDomainService configurationDomainService, final CalendarReadPlatformService calendarReadPlatformService) {
this.meetingRepositoryWrapper = meetingRepositoryWrapper;
this.meetingRepository = meetingRepository;
this.meetingDataValidator = meetingDataValidator;
@@ -87,6 +93,8 @@ public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWrit
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.fromApiJsonHelper = fromApiJsonHelper;
this.configurationDomainService = configurationDomainService;
this.calendarReadPlatformService = calendarReadPlatformService;
}
@Override
@@ -96,11 +104,25 @@ public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWrit
final Date meetingDate = command.DateValueOfParameterNamed(meetingDateParamName);
final Boolean isTransactionDateOnNonMeetingDate = false;
/*Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
isSkipRepaymentOnFirstMonth = isLoanRepaymentsSyncWithMeeting(loan.group(), calendar);
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}*/
try {
final CalendarInstance calendarInstance = getCalendarInstance(command);
// create new meeting
final Meeting newMeeting = Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if(isSkipRepaymentOnFirstMonthEnabled){
if(calendarInstance != null){isSkipRepaymentOnFirstMonth = true;}
if(isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); }
}
final Meeting newMeeting = Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate, isSkipRepaymentOnFirstMonth, numberOfDays);
final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(newMeeting, command);
if (clientsAttendance != null && !clientsAttendance.isEmpty()) {
@@ -214,9 +236,23 @@ public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWrit
@Override
public CommandProcessingResult updateMeeting(final JsonCommand command) {
this.meetingDataValidator.validateForUpdate(command);
final CalendarInstance calendarInstance = getCalendarInstance(command);
// create new meeting
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService
.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
if (calendarInstance != null) {
isSkipRepaymentOnFirstMonth = true;
numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue();
}
}
final Meeting meetingForUpdate = this.meetingRepositoryWrapper.findOneWithNotFoundDetection(command.entityId());
final Map<String, Object> changes = meetingForUpdate.update(command);
final Map<String, Object> changes = meetingForUpdate.update(command, isSkipRepaymentOnFirstMonth, numberOfDays);
try {
if (!changes.isEmpty()) {
@@ -280,10 +316,23 @@ public class MeetingWritePlatformServiceJpaRepositoryImpl implements MeetingWrit
try {
final CalendarInstance calendarInstance = getCalendarInstance(command);
final Meeting meeting = this.meetingRepository.findByCalendarInstanceIdAndMeetingDate(calendarInstance.getId(), meetingDate);
final Meeting meeting = this.meetingRepository
.findByCalendarInstanceIdAndMeetingDate(calendarInstance.getId(), meetingDate);
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService
.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
isSkipRepaymentOnFirstMonth = true;
if (isSkipRepaymentOnFirstMonth) {
numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate()
.intValue();
}
}
// create new meeting
final Meeting newMeeting = (meeting != null) ? meeting : Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate);
final Meeting newMeeting = (meeting != null) ? meeting : Meeting.createNew(calendarInstance, meetingDate, isTransactionDateOnNonMeetingDate,
isSkipRepaymentOnFirstMonth, numberOfDays);
final Collection<ClientAttendance> clientsAttendance = getClientsAttendance(newMeeting, command);
if (clientsAttendance != null && !clientsAttendance.isEmpty()) {
@@ -0,0 +1 @@
insert into c_configuration(name,value,description) values("skip-repayment-on-first-day-of-month",14,"skipping repayment on first day of month");