Repository: incubator-fineract Updated Branches: refs/heads/develop 00e0ea673 -> 1485dc9a8
Center Rescheduling Project: http://git-wip-us.apache.org/repos/asf/incubator-fineract/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-fineract/commit/a01c1e36 Tree: http://git-wip-us.apache.org/repos/asf/incubator-fineract/tree/a01c1e36 Diff: http://git-wip-us.apache.org/repos/asf/incubator-fineract/diff/a01c1e36 Branch: refs/heads/develop Commit: a01c1e361ff2cf91eafe8888102b492f67030476 Parents: 8d160d3 Author: sachinkulkarni12 <[email protected]> Authored: Wed Mar 2 17:12:31 2016 +0530 Committer: sachinkulkarni12 <[email protected]> Committed: Wed Mar 2 17:12:31 2016 +0530 ---------------------------------------------------------------------- .../LoanReschedulingWithinCenterTest.java | 392 +++++++++++++++++++ .../integrationtests/common/CalendarHelper.java | 41 ++ .../data/CalendarHistoryDataWrapper.java | 61 +++ .../portfolio/calendar/domain/Calendar.java | 10 +- ...arWritePlatformServiceJpaRepositoryImpl.java | 14 +- .../loanaccount/data/ScheduleGeneratorDTO.java | 18 +- .../portfolio/loanaccount/domain/Loan.java | 17 +- .../domain/DefaultScheduledDateGenerator.java | 25 +- .../domain/LoanApplicationTerms.java | 32 +- .../loanaccount/service/LoanUtilService.java | 36 +- ...anWritePlatformServiceJpaRepositoryImpl.java | 28 +- 11 files changed, 635 insertions(+), 39 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java new file mode 100644 index 0000000..a825bcb --- /dev/null +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/LoanReschedulingWithinCenterTest.java @@ -0,0 +1,392 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests; + + +import static org.junit.Assert.assertEquals; + +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.apache.fineract.integrationtests.common.CalendarHelper; +import org.apache.fineract.integrationtests.common.CenterDomain; +import org.apache.fineract.integrationtests.common.CenterHelper; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.GroupHelper; +import org.apache.fineract.integrationtests.common.OfficeHelper; +import org.apache.fineract.integrationtests.common.Utils; +import org.apache.fineract.integrationtests.common.accounting.Account; +import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; +import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker; +import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; +import org.apache.fineract.integrationtests.common.organisation.StaffHelper; + +import com.google.gson.Gson; +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; + +public class LoanReschedulingWithinCenterTest { + + private RequestSpecification requestSpec; + private ResponseSpecification responseSpec; + private LoanTransactionHelper loanTransactionHelper; + private ResponseSpecification generalResponseSpec; + private LoanApplicationApprovalTest loanApplicationApprovalTest; + + @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(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + this.loanApplicationApprovalTest = new LoanApplicationApprovalTest(); + this.generalResponseSpec = new ResponseSpecBuilder().build(); + } + + @SuppressWarnings("rawtypes") + @Test + public void testCenterReschedulingLoansWithInterestRecalculationEnabled() { + + Integer officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007"); + String name = "TestFullCreation" + new Timestamp(new java.util.Date().getTime()); + String externalId = Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + int staffId = StaffHelper.createStaff(requestSpec, responseSpec); + int[] groupMembers = generateGroupMembers(1, officeId); + final String centerActivationDate = "01 July 2007"; + Integer centerId = CenterHelper.createCenter(name, officeId, externalId, staffId, groupMembers, centerActivationDate, requestSpec, + responseSpec); + CenterDomain center = CenterHelper.retrieveByID(centerId, requestSpec, responseSpec); + Integer groupId = groupMembers[0]; + Assert.assertNotNull(center); + Assert.assertTrue(center.getStaffId() == staffId); + Assert.assertTrue(center.isActive() == true); + + Integer calendarId = createCalendarMeeting(centerId); + + Integer clientId = createClient(officeId); + + associateClientsToGroup(groupId, clientId); + + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + today.add(Calendar.DAY_OF_MONTH, -14); + // CREATE A LOAN PRODUCT + final String disbursalDate = dateFormat.format(today.getTime()); + final String recalculationRestFrequencyDate = "01 January 2012"; + final boolean isMultiTrancheLoan = false; + + // CREATE LOAN MULTIDISBURSAL PRODUCT WITH INTEREST RECALCULATION + final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan); + + // APPLY FOR TRANCHE LOAN WITH INTEREST RECALCULATION + final Integer loanId = applyForLoanApplicationForInterestRecalculation(clientId, groupId, calendarId, loanProductID, disbursalDate, + recalculationRestFrequencyDate, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0), null); + + // Test for loan account is created + Assert.assertNotNull(loanId); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanId); + + // Test for loan account is created, can be approved + this.loanTransactionHelper.approveLoan(disbursalDate, loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanId); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + // Test for loan account approved can be disbursed + this.loanTransactionHelper.disburseLoan(disbursalDate, loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanId); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------"); + Calendar todaysdate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysdate.add(Calendar.DAY_OF_MONTH, 14); + String oldMeetingDate = dateFormat.format(todaysdate.getTime()); + todaysdate.add(Calendar.DAY_OF_MONTH, 1); + final String centerMeetingNewStartDate = dateFormat.format(todaysdate.getTime()); + CalendarHelper.updateMeetingCalendarForCenter(this.requestSpec, this.responseSpec, centerId, calendarId.toString(), oldMeetingDate, + centerMeetingNewStartDate); + + ArrayList loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, loanId); + // VERIFY RESCHEDULED DATE + ArrayList dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate"); + assertEquals(getDateAsArray(todaysdate, 0), dueDateLoanSchedule); + + // VERIFY THE INTEREST + Float interestDue = (Float) ((HashMap) loanRepaymnetSchedule.get(2)).get("interestDue"); + assertEquals(String.valueOf(interestDue), "90.82"); + + } + + private void associateClientsToGroup(Integer groupId, Integer clientId) { + // Associate client to the group + GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupId.toString(), clientId.toString()); + GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupId, clientId); + } + + private Integer createClient(Integer officeId) { + // CREATE CLIENT + final String clientActivationDate = "01 July 2014"; + Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, clientActivationDate, officeId.toString()); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientId); + return clientId; + } + + private Integer createCalendarMeeting(Integer centerId) { + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + final String startDate = dateFormat.format(today.getTime()); + final String frequency = "2"; // 2:Weekly + final String interval = "2"; // Every one week + final Integer repeatsOnDay = today.get(Calendar.DAY_OF_WEEK) - 1; + + Integer calendarId = CalendarHelper.createMeetingForGroup(this.requestSpec, this.responseSpec, centerId, startDate, frequency, + interval, repeatsOnDay.toString()); + System.out.println("calendarId " + calendarId); + return calendarId; + } + + @SuppressWarnings("rawtypes") + @Test + public void testCenterReschedulingMultiTrancheLoansWithInterestRecalculationEnabled() { + + Integer officeId = new OfficeHelper(requestSpec, responseSpec).createOffice("01 July 2007"); + String name = "TestFullCreation" + new Timestamp(new java.util.Date().getTime()); + String externalId = Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + int staffId = StaffHelper.createStaff(requestSpec, responseSpec); + int[] groupMembers = generateGroupMembers(1, officeId); + final String centerActivationDate = "01 July 2007"; + Integer centerId = CenterHelper.createCenter(name, officeId, externalId, staffId, groupMembers, centerActivationDate, requestSpec, + responseSpec); + CenterDomain center = CenterHelper.retrieveByID(centerId, requestSpec, responseSpec); + Integer groupId = groupMembers[0]; + Assert.assertNotNull(center); + Assert.assertTrue(center.getStaffId() == staffId); + Assert.assertTrue(center.isActive() == true); + + Integer calendarId = createCalendarMeeting(centerId); + + Integer clientId = createClient(officeId); + + associateClientsToGroup(groupId, clientId); + + // CREATE A LOAN PRODUCT + DateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy"); + dateFormat.setTimeZone(Utils.getTimeZoneOfTenant()); + Calendar today = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + today.add(Calendar.DAY_OF_MONTH, -14); + // CREATE A LOAN PRODUCT + final String approveDate = dateFormat.format(today.getTime()); + final String expectedDisbursementDate = dateFormat.format(today.getTime()); + final String disbursementDate = dateFormat.format(today.getTime()); + final String approvalAmount = "10000"; + final String recalculationRestFrequencyDate = "01 January 2012"; + final boolean isMultiTrancheLoan = true; + + // CREATE LOAN MULTIDISBURSAL PRODUCT WITH INTEREST RECALCULATION + final Integer loanProductID = createLoanProductWithInterestRecalculation(LoanProductTestBuilder.RBI_INDIA_STRATEGY, + LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE, + LoanProductTestBuilder.RECALCULATION_STRATEGY_REDUCE_NUMBER_OF_INSTALLMENTS, + LoanProductTestBuilder.RECALCULATION_FREQUENCY_TYPE_DAILY, "0", recalculationRestFrequencyDate, + LoanProductTestBuilder.INTEREST_APPLICABLE_STRATEGY_ON_PRE_CLOSE_DATE, null, isMultiTrancheLoan); + + // CREATE TRANCHES + List<HashMap> createTranches = new ArrayList<>(); + createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail(disbursementDate, "5000")); + createTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("25 June 2016", "5000")); + + // APPROVE TRANCHES + List<HashMap> approveTranches = new ArrayList<>(); + approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail(disbursementDate, "5000")); + approveTranches.add(this.loanApplicationApprovalTest.createTrancheDetail("25 June 2016", "5000")); + + // APPLY FOR TRANCHE LOAN WITH INTEREST RECALCULATION + final Integer loanID = applyForLoanApplicationForInterestRecalculation(clientId, groupId, calendarId, loanProductID, + disbursementDate, recalculationRestFrequencyDate, LoanApplicationTestBuilder.RBI_INDIA_STRATEGY, new ArrayList<HashMap>(0), + createTranches); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + + // VALIDATE THE LOAN STATUS + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + System.out.println("-----------------------------------APPROVE LOAN-----------------------------------------------------------"); + loanStatusHashMap = this.loanTransactionHelper.approveLoanWithApproveAmount(approveDate, expectedDisbursementDate, approvalAmount, + loanID, approveTranches); + + // VALIDATE THE LOAN IS APPROVED + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap); + + // DISBURSE A FIRST TRANCHE + this.loanTransactionHelper.disburseLoan(disbursementDate, loanID); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID); + + System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------"); + Calendar todaysdate = Calendar.getInstance(Utils.getTimeZoneOfTenant()); + todaysdate.add(Calendar.DAY_OF_MONTH, 14); + String oldMeetingDate = dateFormat.format(todaysdate.getTime()); + todaysdate.add(Calendar.DAY_OF_MONTH, 1); + final String centerMeetingNewStartDate = dateFormat.format(todaysdate.getTime()); + CalendarHelper.updateMeetingCalendarForCenter(this.requestSpec, this.responseSpec, centerId, calendarId.toString(), oldMeetingDate, + centerMeetingNewStartDate); + + ArrayList loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, loanID); + // VERIFY RESCHEDULED DATE + ArrayList dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate"); + assertEquals(getDateAsArray(todaysdate, 0), dueDateLoanSchedule); + + // VERIFY THE INTEREST + Float interestDue = (Float) ((HashMap) loanRepaymnetSchedule.get(2)).get("interestDue"); + assertEquals(String.valueOf(interestDue), "41.05"); + + } + + private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, + final String recalculationRestFrequencyDate, final String preCloseInterestCalculationStrategy, final Account[] accounts, + final boolean isMultiTrancheLoan) { + final String recalculationCompoundingFrequencyType = null; + final String recalculationCompoundingFrequencyInterval = null; + final String recalculationCompoundingFrequencyDate = null; + return createLoanProductWithInterestRecalculation(repaymentStrategy, interestRecalculationCompoundingMethod, + rescheduleStrategyMethod, recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyDate, recalculationCompoundingFrequencyType, recalculationCompoundingFrequencyInterval, + recalculationCompoundingFrequencyDate, preCloseInterestCalculationStrategy, accounts, null, false, isMultiTrancheLoan); + } + + private Integer createLoanProductWithInterestRecalculation(final String repaymentStrategy, + final String interestRecalculationCompoundingMethod, final String rescheduleStrategyMethod, + final String recalculationRestFrequencyType, final String recalculationRestFrequencyInterval, + final String recalculationRestFrequencyDate, final String recalculationCompoundingFrequencyType, + final String recalculationCompoundingFrequencyInterval, final String recalculationCompoundingFrequencyDate, + final String preCloseInterestCalculationStrategy, final Account[] accounts, final String chargeId, + boolean isArrearsBasedOnOriginalSchedule, final boolean isMultiTrancheLoan) { + System.out.println("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------"); + LoanProductTestBuilder builder = new LoanProductTestBuilder() + .withPrincipal("10000.00") + .withNumberOfRepayments("12") + .withRepaymentAfterEvery("2") + .withRepaymentTypeAsWeek() + .withinterestRatePerPeriod("2") + .withInterestRateFrequencyTypeAsMonths() + .withTranches(isMultiTrancheLoan) + .withInterestCalculationPeriodTypeAsRepaymentPeriod(true) + .withRepaymentStrategy(repaymentStrategy) + .withInterestTypeAsDecliningBalance() + .withInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod, + preCloseInterestCalculationStrategy) + .withInterestRecalculationRestFrequencyDetails(recalculationRestFrequencyType, recalculationRestFrequencyInterval, + recalculationRestFrequencyDate) + .withInterestRecalculationCompoundingFrequencyDetails(recalculationCompoundingFrequencyType, + recalculationCompoundingFrequencyInterval, recalculationCompoundingFrequencyDate); + if (accounts != null) { + builder = builder.withAccountingRulePeriodicAccrual(accounts); + } + + if (isArrearsBasedOnOriginalSchedule) builder = builder.withArrearsConfiguration(); + + final String loanProductJSON = builder.build(chargeId); + return this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + @SuppressWarnings("rawtypes") + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, Integer groupId, Integer calendarId, + final Integer loanProductID, final String disbursementDate, final String restStartDate, final String repaymentStrategy, + final List<HashMap> charges, List<HashMap> tranches) { + final String graceOnInterestPayment = null; + final String compoundingStartDate = null; + final String graceOnPrincipalPayment = null; + return applyForLoanApplicationForInterestRecalculation(clientID, groupId, calendarId, loanProductID, disbursementDate, + restStartDate, compoundingStartDate, repaymentStrategy, charges, graceOnInterestPayment, graceOnPrincipalPayment, tranches); + } + + @SuppressWarnings({ "rawtypes", "unused" }) + private Integer applyForLoanApplicationForInterestRecalculation(final Integer clientID, Integer groupId, Integer calendarId, + final Integer loanProductID, final String disbursementDate, final String restStartDate, final String compoundingStartDate, + final String repaymentStrategy, final List<HashMap> charges, final String graceOnInterestPayment, + final String graceOnPrincipalPayment, List<HashMap> tranches) { + System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal("10000.00") // + .withLoanTermFrequency("24") // + .withLoanTermFrequencyAsWeeks() // + .withNumberOfRepayments("12") // + .withRepaymentEveryAfter("2") // + .withRepaymentFrequencyTypeAsWeeks() // + .withInterestRatePerPeriod("2").withLoanType("jlg") // + .withCalendarID(calendarId.toString()).withAmortizationTypeAsEqualInstallments() // + .withFixedEmiAmount("") // + .withTranches(tranches).withInterestTypeAsDecliningBalance() // + .withInterestCalculationPeriodTypeAsDays() // + .withInterestCalculationPeriodTypeAsDays() // + .withExpectedDisbursementDate(disbursementDate) // + .withSubmittedOnDate(disbursementDate) // + .withRestFrequencyDate(restStartDate)// + .withwithRepaymentStrategy(repaymentStrategy) // + .withCharges(charges)// + .build(clientID.toString(), groupId.toString(), loanProductID.toString(), null); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private int[] generateGroupMembers(int size, int officeId) { + int[] groupMembers = new int[size]; + for (int i = 0; i < groupMembers.length; i++) { + final HashMap<String, String> map = new HashMap<>(); + map.put("officeId", "" + officeId); + map.put("name", Utils.randomStringGenerator("Group_Name_", 5)); + map.put("externalId", Utils.randomStringGenerator("ID_", 7, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("locale", "en"); + map.put("active", "true"); + map.put("activationDate", "04 March 2011"); + + groupMembers[i] = Utils.performServerPost(requestSpec, responseSpec, "/fineract-provider/api/v1/groups?" + + Utils.TENANT_IDENTIFIER, new Gson().toJson(map), "groupId"); + } + return groupMembers; + } + + private List getDateAsArray(Calendar date, int addPeriod) { + return getDateAsArray(date, addPeriod, Calendar.DAY_OF_MONTH); + } + + private List getDateAsArray(Calendar date, int addvalue, int type) { + date.add(type, addvalue); + return new ArrayList<>(Arrays.asList(date.get(Calendar.YEAR), date.get(Calendar.MONTH) + 1, date.get(Calendar.DAY_OF_MONTH))); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java index 0e9423b..e4bac93 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/CalendarHelper.java @@ -32,6 +32,8 @@ public class CalendarHelper { private static final String BASE_URL = "/fineract-provider/api/v1/"; private static final String PARENT_ENTITY_NAME = "groups/"; private static final String ENITY_NAME = "/calendars"; + private static final String Center_Entity = "centers/"; + private static final String Edit_Calendar = "editcalendarbasedonmeetingdates/"; public static Integer createMeetingCalendarForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, final Integer groupId, final String startDate, final String frequency, final String interval, final String repeatsOnDay) { @@ -86,4 +88,43 @@ public class CalendarHelper { final Integer responseCalendarId = from(responseCalendarDetailsinJSON).get("id"); assertEquals("ERROR IN CREATING THE CALENDAR", generatedCalendarId, responseCalendarId); } + + public static Integer createMeetingForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final Integer groupId, final String startDate, final String frequency, final String interval, final String repeatsOnDay) { + + System.out.println("---------------------------------CREATING A MEETING CALENDAR FOR THE GROUP------------------------------"); + + final String CALENDAR_RESOURCE_URL = BASE_URL + Center_Entity + groupId + ENITY_NAME + "?" + Utils.TENANT_IDENTIFIER; + + System.out.println(CALENDAR_RESOURCE_URL); + + return Utils.performServerPost(requestSpec, responseSpec, CALENDAR_RESOURCE_URL, + getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate), "resourceId"); + } + + public static Integer updateMeetingCalendarForCenter(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + Integer centerId, String calendarID, String oldDate, String startDate) { + + System.out.println("---------------------------------UPADATING A MEETING CALENDAR FOR THE CENTER------------------------------"); + + final String CALENDAR_RESOURCE_URL = BASE_URL + Center_Entity + centerId + ENITY_NAME + "/" + calendarID + "?" + + Utils.TENANT_IDENTIFIER; + + System.out.println(CALENDAR_RESOURCE_URL); + + return Utils.performServerPut(requestSpec, responseSpec, CALENDAR_RESOURCE_URL, getTestCalendarMeetingAsJSON(oldDate, startDate), + "resourceId"); + + } + + private static String getTestCalendarMeetingAsJSON(String oldDate, String startDate) { + final HashMap<String, String> map = new HashMap<>(); + map.put("dateFormat", "dd MMMM yyyy"); + map.put("locale", "en"); + map.put("newMeetingDate", startDate); + map.put("presentMeetingDate", oldDate); + map.put("reschedulebasedOnMeetingDates", "true"); + System.out.println("map : " + map); + return new Gson().toJson(map); + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java new file mode 100644 index 0000000..6e8e678 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/data/CalendarHistoryDataWrapper.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.calendar.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; +import org.joda.time.LocalDate; + + +public class CalendarHistoryDataWrapper { + + private final List<CalendarHistory> calendarHistoryList; + + public CalendarHistoryDataWrapper(final Set<CalendarHistory> calendarHistoryList){ + this.calendarHistoryList = new ArrayList<>(); + this.calendarHistoryList.addAll(calendarHistoryList); + final Comparator<CalendarHistory> orderByDate = new Comparator<CalendarHistory>() { + @Override + public int compare(CalendarHistory calendarHistory1, CalendarHistory calendarHistory2) { + return calendarHistory1.getEndDateLocalDate().compareTo(calendarHistory2.getEndDateLocalDate()); + } + }; + Collections.sort(this.calendarHistoryList, orderByDate); + } + + public CalendarHistory getCalendarHistory(final LocalDate dueRepaymentPeriodDate) { + CalendarHistory calendarHistory = null; + for (CalendarHistory history : this.calendarHistoryList) { + if (history.getEndDateLocalDate().isAfter(dueRepaymentPeriodDate)) { + calendarHistory = history; + break; + } + } + return calendarHistory; + } + + public List<CalendarHistory> getCalendarHistoryList(){ + return this.calendarHistoryList; + } +} http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java index 103b96f..98e8096 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/domain/Calendar.java @@ -95,7 +95,7 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> { @OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "calendar_id") - private final Set<CalendarHistory> calendarHistory = new HashSet<>(); + private Set<CalendarHistory> calendarHistory = new HashSet<>(); protected Calendar() { @@ -581,4 +581,12 @@ public class Calendar extends AbstractAuditableCustom<AppUser, Long> { this.startDate = startDate.toDate(); this.endDate = endDate.toDate(); } + + public Set<CalendarHistory> getCalendarHistory(){ + return this.calendarHistory; + } + + public void updateCalendarHistory(final Set<CalendarHistory> calendarHistory){ + this.calendarHistory = calendarHistory; + } } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java index 7e59642..438c687 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarWritePlatformServiceJpaRepositoryImpl.java @@ -24,6 +24,7 @@ import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.JsonCommand; @@ -239,7 +240,6 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr if (calendarForUpdate == null) { throw new CalendarNotFoundException(calendarId); } final Date oldStartDate = calendarForUpdate.getStartDate(); - final LocalDate currentDate = DateUtils.getLocalDateOfTenant(); // create calendar history before updating calendar final CalendarHistory calendarHistory = new CalendarHistory(calendarForUpdate, oldStartDate); @@ -288,12 +288,12 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr if (!changes.isEmpty()) { // update calendar history table only if there is a change in // calendar start date. - if (currentDate.isAfter(new LocalDate(oldStartDate))) { - final Date endDate = calendarForUpdate.getStartDateLocalDate().minusDays(1).toDate(); - calendarHistory.updateEndDate(endDate); - this.calendarHistoryRepository.save(calendarHistory); - } - + final Date endDate = presentMeetingDate.minusDays(1).toDate(); + calendarHistory.updateEndDate(endDate); + this.calendarHistoryRepository.save(calendarHistory); + Set<CalendarHistory> history = calendarForUpdate.getCalendarHistory(); + history.add(calendarHistory); + calendarForUpdate.updateCalendarHistory(history); this.calendarRepository.saveAndFlush(calendarForUpdate); if (this.configurationDomainService.isRescheduleFutureRepaymentsEnabled() && calendarForUpdate.isRepeating()) { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java index dde8599..a8f7a35 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/ScheduleGeneratorDTO.java @@ -19,6 +19,8 @@ package org.apache.fineract.portfolio.loanaccount.data; import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; +import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.floatingrates.data.FloatingRateDTO; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; @@ -35,11 +37,14 @@ public class ScheduleGeneratorDTO { LocalDate recalculateFrom; final Long overdurPenaltyWaitPeriod; final FloatingRateDTO floatingRateDTO; + final Calendar calendar; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper; 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 LocalDate recalculateFrom, final Long overdurPenaltyWaitPeriod, final FloatingRateDTO floatingRateDTO, + final Calendar calendar, final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { this.loanScheduleFactory = loanScheduleFactory; this.applicationCurrency = applicationCurrency; @@ -50,6 +55,9 @@ public class ScheduleGeneratorDTO { this.overdurPenaltyWaitPeriod = overdurPenaltyWaitPeriod; this.holidayDetailDTO = holidayDetailDTO; this.floatingRateDTO = floatingRateDTO; + this.calendar = calendar; + this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; + } public LoanScheduleGeneratorFactory getLoanScheduleFactory() { @@ -99,5 +107,13 @@ public class ScheduleGeneratorDTO { public FloatingRateDTO getFloatingRateDTO() { return this.floatingRateDTO; } + + public Calendar getCalendar(){ + return this.calendar; + } + + public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper(){ + return this.calendarHistoryDataWrapper; + } } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 56fd87c..78e284e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -71,7 +71,9 @@ import org.apache.fineract.organisation.staff.domain.Staff; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; +import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.charge.domain.Charge; @@ -5032,6 +5034,8 @@ public class Loan extends AbstractPersistable<Long> { InterestRecalculationCompoundingMethod compoundingMethod = null; RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + Calendar calendar = null; + CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); @@ -5039,6 +5043,8 @@ public class Loan extends AbstractPersistable<Long> { compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); + calendar = scheduleGeneratorDTO.getCalendar(); + calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); } BigDecimal annualNominalInterestRate = this.loanRepaymentScheduleDetail.getAnnualNominalInterestRate(); @@ -5053,7 +5059,7 @@ public class Loan extends AbstractPersistable<Long> { this.maxOutstandingLoanBalance, getInterestChargedFromDate(), this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), - rescheduleStrategyMethod, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations); + rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper); return loanApplicationTerms; } @@ -5269,6 +5275,12 @@ public class Loan extends AbstractPersistable<Long> { final BigDecimal maxOutstandingBalance = getMaxOutstandingLoanBalance(); final List<DisbursementData> disbursementData = getDisbursmentData(); + + CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + if (loanCalendar != null) { + Set<CalendarHistory> calendarHistory = loanCalendar.getCalendarHistory(); + calendarHistoryDataWrapper = new CalendarHistoryDataWrapper(calendarHistory); + } RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; @@ -5290,7 +5302,8 @@ public class Loan extends AbstractPersistable<Long> { maxOutstandingBalance, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), - rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations); + rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, + calendarHistoryDataWrapper); } /** http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 0a8ac12..a23f1e4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -18,10 +18,13 @@ */ package org.apache.fineract.portfolio.loanaccount.loanschedule.domain; + import org.apache.fineract.organisation.holiday.service.HolidayUtil; import org.apache.fineract.organisation.workingdays.domain.RepaymentRescheduleType; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; +import org.apache.fineract.portfolio.calendar.domain.CalendarHistory; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.common.domain.DayOfWeekType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; @@ -68,9 +71,25 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { // calendar associated with // the loan, and we should use it in order to calculate next // repayment - LocalDate seedDate = currentCalendar.getStartDateLocalDate(); - String reccuringString = currentCalendar.getRecurrence(); - dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, dueRepaymentPeriodDate, + + CalendarHistory calendarHistory = null; + CalendarHistoryDataWrapper calendarHistoryDataWrapper = loanApplicationTerms.getCalendarHistoryDataWrapper(); + if(calendarHistoryDataWrapper != null){ + calendarHistory = loanApplicationTerms.getCalendarHistoryDataWrapper().getCalendarHistory(dueRepaymentPeriodDate); + } + + // get the start date from the calendar history + LocalDate seedDate = null; + String reccuringString = null; + if (calendarHistory == null) { + seedDate = currentCalendar.getStartDateLocalDate(); + reccuringString = currentCalendar.getRecurrence(); + } else { + seedDate = calendarHistory.getStartDateLocalDate(); + reccuringString = calendarHistory.getRecurrence(); + } + + dueRepaymentPeriodDate = CalendarUtils.getNewRepaymentMeetingDate(reccuringString, seedDate, lastRepaymentDate.plusDays(1), loanApplicationTerms.getRepaymentEvery(), CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()), holidayDetailDTO.getWorkingDays()); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index c33091c..80e2530 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -27,6 +27,7 @@ import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency; import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.common.domain.DayOfWeekType; @@ -177,6 +178,8 @@ public final class LoanApplicationTerms { private Money adjustPrincipalForFlatLoans; private final LocalDate seedDate; + + private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -198,6 +201,7 @@ public final class LoanApplicationTerms { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -207,7 +211,7 @@ public final class LoanApplicationTerms { graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); + preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -224,13 +228,14 @@ public final class LoanApplicationTerms { final LoanRescheduleStrategyMethod rescheduleStrategyMethod, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, List<LoanTermVariationsData> loanTermVariations) { final Calendar loanCalendar = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance, loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy, - rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations); + rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -245,7 +250,8 @@ public final class LoanApplicationTerms { final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar, BigDecimal approvedAmount, - BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) { + BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -277,7 +283,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -289,8 +295,8 @@ public final class LoanApplicationTerms { final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, BigDecimal approvedAmount, - final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) { + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final Calendar loanCalendar, + BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List<LoanTermVariationsData> loanTermVariations) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -319,7 +325,8 @@ public final class LoanApplicationTerms { rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod(); interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod(); } - final Calendar loanCalendar = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -329,7 +336,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations); + loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper); } public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms, @@ -351,7 +358,7 @@ public final class LoanApplicationTerms { applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy, applicationTerms.loanCalendar, - applicationTerms.approvedPrincipal.getAmount(), loanTermVariations); + applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, applicationTerms.calendarHistoryDataWrapper); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -372,7 +379,7 @@ 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) { + BigDecimal approvedAmount, List<LoanTermVariationsData> loanTermVariations, final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -430,6 +437,7 @@ public final class LoanApplicationTerms { } else { this.seedDate = this.calculatedRepaymentsStartingFromDate; } + this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1421,5 +1429,9 @@ public final class LoanApplicationTerms { public LocalDate getSeedDate() { return this.seedDate; } + + public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper(){ + return this.calendarHistoryDataWrapper; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java index 423e346..103209b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanUtilService.java @@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.util.List; +import java.util.Set; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.organisation.holiday.domain.Holiday; @@ -30,8 +31,10 @@ import org.apache.fineract.organisation.monetary.domain.ApplicationCurrencyRepos import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.apache.fineract.portfolio.calendar.data.CalendarHistoryDataWrapper; import org.apache.fineract.portfolio.calendar.domain.Calendar; 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.CalendarUtils; @@ -88,8 +91,15 @@ public class LoanUtilService { ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency); final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); + Calendar calendar = null; + CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + if (calendarInstance != null) { + calendar = calendarInstance.getCalendar(); + Set<CalendarHistory> calendarHistory = calendar.getCalendarHistory(); + calendarHistoryDataWrapper = new CalendarHistoryDataWrapper(calendarHistory); + } LocalDate calculatedRepaymentsStartingFromDate = this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, - calendarInstance); + calendarInstance, calendarHistoryDataWrapper); CalendarInstance restCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null; Long overdurPenaltyWaitPeriod = null; @@ -103,7 +113,7 @@ public class LoanUtilService { FloatingRateDTO floatingRateDTO = constructFloatingRateDTO(loan); ScheduleGeneratorDTO scheduleGeneratorDTO = new ScheduleGeneratorDTO(loanScheduleFactory, applicationCurrency, calculatedRepaymentsStartingFromDate, holidayDetails, restCalendarInstance, compoundingCalendarInstance, recalculateFrom, - overdurPenaltyWaitPeriod, floatingRateDTO); + overdurPenaltyWaitPeriod, floatingRateDTO, calendar, calendarHistoryDataWrapper); return scheduleGeneratorDTO; } @@ -111,7 +121,8 @@ public class LoanUtilService { public LocalDate getCalculatedRepaymentsStartingFromDate(final Loan loan) { final CalendarInstance calendarInstance = this.calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); - return this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, calendarInstance); + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + return this.getCalculatedRepaymentsStartingFromDate(loan.getDisbursementDate(), loan, calendarInstance, calendarHistoryDataWrapper); } private HolidayDetailDTO constructHolidayDTO(final Loan loan) { @@ -146,22 +157,33 @@ public class LoanUtilService { } private LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, - final CalendarInstance calendarInstance) { + final CalendarInstance calendarInstance, final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { final Calendar calendar = calendarInstance == null ? null : calendarInstance.getCalendar(); - return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar); + return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar, calendarHistoryDataWrapper); } public LocalDate getCalculatedRepaymentsStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar) { + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; if (calendar == null) { return getCalculatedRepaymentsStartingFromDate(loan); } - return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar); + return calculateRepaymentStartingFromDate(actualDisbursementDate, loan, calendar, calendarHistoryDataWrapper); } - private LocalDate calculateRepaymentStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar) { + private LocalDate calculateRepaymentStartingFromDate(final LocalDate actualDisbursementDate, final Loan loan, final Calendar calendar, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper) { LocalDate calculatedRepaymentsStartingFromDate = loan.getExpectedFirstRepaymentOnDate(); if (calendar != null) {// sync repayments + if (calculatedRepaymentsStartingFromDate == null && !calendar.getCalendarHistory().isEmpty() && + calendarHistoryDataWrapper != null) { + for (CalendarHistory calendarHistory : calendarHistoryDataWrapper.getCalendarHistoryList()) { + calculatedRepaymentsStartingFromDate = calendarHistory.getStartDateLocalDate(); + break; + } + return calculatedRepaymentsStartingFromDate; + } + // TODO: AA - user provided first repayment date takes precedence // over recalculated meeting date if (calculatedRepaymentsStartingFromDate == null) { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/a01c1e36/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 06ae1b1..dddc901 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -83,7 +83,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarInstance; import org.apache.fineract.portfolio.calendar.domain.CalendarInstanceRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarRepository; import org.apache.fineract.portfolio.calendar.domain.CalendarType; -import org.apache.fineract.portfolio.calendar.exception.CalendarParameterUpdateNotSupportedException; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode; import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper; @@ -130,8 +129,10 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin import org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; +import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; @@ -219,6 +220,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final BusinessEventNotifierService businessEventNotifierService; private final GuarantorDomainService guarantorDomainService; private final LoanUtilService loanUtilService; + private final LoanSummaryWrapper loanSummaryWrapper; + private final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -245,7 +248,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final AccountAssociationsRepository accountAssociationRepository, final AccountTransferDetailRepository accountTransferDetailRepository, final BusinessEventNotifierService businessEventNotifierService, final GuarantorDomainService guarantorDomainService, - final LoanUtilService loanUtilService) { + final LoanUtilService loanUtilService, final LoanSummaryWrapper loanSummaryWrapper, + final LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy) { this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; @@ -280,6 +284,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.businessEventNotifierService = businessEventNotifierService; this.guarantorDomainService = guarantorDomainService; this.loanUtilService = loanUtilService; + this.loanSummaryWrapper = loanSummaryWrapper; + this.transactionProcessingStrategy = transactionProcessingStrategy; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -1955,6 +1961,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final WorkingDays workingDays = this.workingDaysRepository.findOne(); + final AppUser currentUser = getAppUserIfPresent(); + final List<Long> existingTransactionIds = new ArrayList<>(); + final List<Long> existingReversedTransactionIds = new ArrayList<>(); final Collection<Integer> loanStatuses = new ArrayList<>(Arrays.asList(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), LoanStatus.APPROVED.getValue(), LoanStatus.ACTIVE.getValue())); final Collection<Integer> loanTypes = new ArrayList<>(Arrays.asList(AccountType.GROUP.getValue(), AccountType.JLG.getValue())); @@ -1966,16 +1975,19 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final List<Loan> loans = this.loanRepository.findByIdsAndLoanStatusAndLoanType(loanIds, loanStatuses, loanTypes); List<Holiday> holidays = null; + final LocalDate recalculateFrom = null; // loop through each loan to reschedule the repayment dates for (final Loan loan : loans) { if (loan != null) { - if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { - final String defaultUserMessage = "Meeting calendar type update is not supported"; - throw new CalendarParameterUpdateNotSupportedException("jlg.loan.recalculation", defaultUserMessage); - } holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loan.getOfficeId(), loan.getDisbursementDate().toDate()); - - if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) { + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); + loan.setHelpers(null, this.loanSummaryWrapper, this.transactionProcessingStrategy); + loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, + existingReversedTransactionIds, currentUser); + this.loanScheduleHistoryWritePlatformService.createAndSaveLoanScheduleArchive( + loan.fetchRepaymentScheduleInstallments(), loan, null); + } else if (reschedulebasedOnMeetingDates != null && reschedulebasedOnMeetingDates) { loan.updateLoanRepaymentScheduleDates(calendar.getStartDateLocalDate(), calendar.getRecurrence(), isHolidayEnabled, holidays, workingDays, reschedulebasedOnMeetingDates, presentMeetingDate, newMeetingDate); } else {
