Skip Repayment Date Falling On First Day of Month
This commit is contained in:
+151
@@ -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"));
|
||||
}
|
||||
|
||||
}
|
||||
+6
-1
@@ -70,6 +70,11 @@ public interface ConfigurationDomainService {
|
||||
Date retrieveOrganisationStartDate();
|
||||
|
||||
boolean isPaymnetypeApplicableforDisbursementCharge();
|
||||
|
||||
|
||||
boolean isInterestChargedFromDateSameAsDisbursementDate();
|
||||
|
||||
boolean isSkippingMeetingOnFirstDayOfMonthEnabled();
|
||||
|
||||
Long retreivePeroidInNumberOfDaysForSkipMeetingDate();
|
||||
|
||||
}
|
||||
+13
@@ -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() {
|
||||
|
||||
+2
-2
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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;
|
||||
|
||||
+5
@@ -142,4 +142,9 @@ public class CalendarHistory extends AbstractPersistable<Long> {
|
||||
public void updateEndDate(Date historyCalEndDate) {
|
||||
this.endDate = historyCalEndDate;
|
||||
}
|
||||
|
||||
public Calendar getCalendar() {
|
||||
return this.calendar;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -50,4 +50,6 @@ public interface CalendarReadPlatformService {
|
||||
|
||||
LocalDate generateNextEligibleMeetingDateForCollection(CalendarData calendarData, MeetingData lastMeetingData);
|
||||
|
||||
Boolean isCalendarAssociatedWithEntity(final Long entityId, final Long calendarId, Long entityTypeId);
|
||||
|
||||
}
|
||||
|
||||
+62
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+75
-20
@@ -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());
|
||||
|
||||
+35
-5
@@ -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)
|
||||
|
||||
+14
-2
@@ -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);
|
||||
|
||||
+16
-2
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-10
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-2
@@ -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();
|
||||
|
||||
+3
-1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+35
-12
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
+36
-11
@@ -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,
|
||||
|
||||
+4
-3
@@ -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:
|
||||
|
||||
+2
-1
@@ -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);
|
||||
}
|
||||
|
||||
+13
-2
@@ -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;
|
||||
|
||||
+9
-1
@@ -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();
|
||||
|
||||
+3
-2
@@ -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.";
|
||||
|
||||
+10
-2
@@ -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,
|
||||
|
||||
+56
-5
@@ -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;
|
||||
|
||||
+19
-7
@@ -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);
|
||||
|
||||
+9
-6
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+56
-7
@@ -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()) {
|
||||
|
||||
+1
@@ -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");
|
||||
Reference in New Issue
Block a user