From dc672153acb63e6f78ac6aad7324690cb6ead6b0 Mon Sep 17 00:00:00 2001 From: Peter Naftaliev Date: Fri, 14 Aug 2015 20:34:19 +0300 Subject: [PATCH 1/2] Finished fixing group loan date change and rechduling date issues. Bug: MIFOSX-1765 --- .../DisbursalAndRepaymentScheduleTest.java | 353 ++++++++++++++++++ .../common/CalendarHelper.java | 43 ++- .../loans/LoanApplicationTestBuilder.java | 54 ++- .../LoanRescheduleRequestTestBuilder.java | 229 ++++++------ .../portfolio/loanaccount/domain/Loan.java | 96 ++--- .../domain/AbstractLoanScheduleGenerator.java | 48 +-- .../domain/DefaultScheduledDateGenerator.java | 37 +- .../domain/LoanApplicationTerms.java | 49 ++- .../domain/LoanScheduleGenerator.java | 7 +- .../domain/ScheduledDateGenerator.java | 3 +- .../service/LoanScheduleAssembler.java | 2 +- .../domain/DefaultLoanReschedulerFactory.java | 7 +- .../domain/LoanReschedulerFactory.java | 4 +- ...nReschedulePreviewPlatformServiceImpl.java | 11 +- ...heduleRequestWritePlatformServiceImpl.java | 44 +-- 15 files changed, 710 insertions(+), 277 deletions(-) create mode 100644 mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/DisbursalAndRepaymentScheduleTest.java diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/DisbursalAndRepaymentScheduleTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/DisbursalAndRepaymentScheduleTest.java new file mode 100644 index 000000000..0d59bca0a --- /dev/null +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/DisbursalAndRepaymentScheduleTest.java @@ -0,0 +1,353 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.integrationtests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +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.mifosplatform.integrationtests.common.*; +import org.mifosplatform.integrationtests.common.loans.*; + +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; + +/** + * Tests loan schedule change based on group meeting changes and loan + * rescheduling + **/ +@SuppressWarnings({ "rawtypes" }) +public class DisbursalAndRepaymentScheduleTest { + + private ResponseSpecification responseSpec; + private ResponseSpecification responseSpecForStatusCode403; + private ResponseSpecification generalResponseSpec; + private RequestSpecification requestSpec; + private LoanTransactionHelper loanTransactionHelper; + private LoanRescheduleRequestHelper loanRescheduleRequestHelper; + private Integer loanRescheduleRequestId; + private Integer clientId; + private Integer groupId; + private Integer groupCalendarId; + private Integer loanProductId; + private Integer loanId; + private final String loanPrincipalAmount = "100000.00"; + private final String numberOfRepayments = "12"; + private final String interestRatePerPeriod = "18"; + + private final SimpleDateFormat dateFormatterMifosStandard = new SimpleDateFormat("dd MMMM yyyy"); + + @Before + public void setup() { + Utils.initializeRESTAssured(); + } + + @Test + public void testRescheduleJLGLoanSynk() { + System.out.println("---------------------------------STARTING RESCHEDULE JLG LOAN TEST ------------------------------------------"); + + Calendar meetingCalendar = Calendar.getInstance(); + meetingCalendar.setFirstDayOfWeek(Calendar.MONDAY); + meetingCalendar.setTime(new java.util.Date()); + + int today = meetingCalendar.get(Calendar.DAY_OF_WEEK); + // making sure that the meeting calendar is set for the coming monday. + if (today >= Calendar.MONDAY) { + meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today + 7)); + } else { + meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today)); + } + + Calendar groupMeetingChangeCalendar = (Calendar) meetingCalendar.clone(); + + meetingCalendar.add(Calendar.WEEK_OF_YEAR, -3); + + final String groupMeetingDate = this.dateFormatterMifosStandard.format(meetingCalendar.getTime()); + + final String disbursalDate = groupMeetingDate; // first meeting date + // after group creation + + final String rescheduleSubmittedDate = this.dateFormatterMifosStandard.format(new java.util.Date()); + + final String loanType = "jlg"; + final String rescheduleInterestRate = "28.0"; + groupMeetingChangeCalendar.add(Calendar.DAY_OF_YEAR, 1); + final String groupMeetingNewStartDate = this.dateFormatterMifosStandard.format(groupMeetingChangeCalendar.getTime()); + // The date + // from + // which we + // start the + // new group + // meeting + // occasion, + // this is a + // tuesday. + groupMeetingChangeCalendar.add(Calendar.WEEK_OF_YEAR, 2); + final String rescheduleDate = this.dateFormatterMifosStandard.format(groupMeetingChangeCalendar.getTime()); + + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.requestSpec.header("X-Mifos-Platform-TenantId", "default"); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.generalResponseSpec = new ResponseSpecBuilder().build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec); + System.out.println("---------------------------------CREATING ENTITIES AND JLG LOAN ------------------------------------------"); + // create all required entities + this.createRequiredEntitiesForJLGLoanSync(groupMeetingDate); + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount).withLoanTermFrequency("24") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("12").withRepaymentEveryAfter("2") + .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays() + .withInterestRatePerPeriod(interestRatePerPeriod).withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate) + .withExpectedDisbursementDate(disbursalDate).withLoanType(loanType).withSyncDisbursementWithMeetin() + .withCalendarID(this.groupCalendarId.toString()) + .build(this.clientId.toString(), this.groupId.toString(), this.loanProductId.toString(), null); + + this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + // Test for loan account is created + Assert.assertNotNull(this.loanId); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + // Test for loan account is created, can be approved + this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + // Test for loan account approved can be disbursed + this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------"); + CalendarHelper.updateMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, this.groupCalendarId.toString(), + groupMeetingNewStartDate, "2", "2", "2"); // New meeting dates + // will be the tuesday + // after the + // coming + // monday + + ArrayList loanRepaymnetSchedule = this.loanTransactionHelper + .getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId); + + ArrayList dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate"); + Calendar dueDateCalendar = Calendar.getInstance(); + dueDateCalendar.setFirstDayOfWeek(Calendar.MONDAY); + dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1, + (Integer) dueDateLoanSchedule.get(2)); + assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY", 3, dueDateCalendar.get(Calendar.DAY_OF_WEEK)); + + System.out.println("---------------------------------CREATING LOAN RESCHEDULE REQUEST------------------------------------------"); + + String requestJSON = new LoanRescheduleRequestTestBuilder().updateGraceOnInterest("2").updateGraceOnPrincipal("2") + .updateNewInterestRate(rescheduleInterestRate).updateRescheduleFromDate(rescheduleDate) + .updateSubmittedOnDate(rescheduleSubmittedDate).build(this.loanId.toString()); + + this.loanRescheduleRequestId = this.loanRescheduleRequestHelper.createLoanRescheduleRequest(requestJSON); + this.loanRescheduleRequestHelper.verifyCreationOfLoanRescheduleRequest(this.loanRescheduleRequestId); + + loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId); + dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate"); + dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1, + (Integer) dueDateLoanSchedule.get(2)); + assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY, EVEN AFTER LOAN RESCHEDULE REQUEST WAS SENT", 3, + dueDateCalendar.get(Calendar.DAY_OF_WEEK)); + + System.out.println("Successfully created loan reschedule request (ID: " + this.loanRescheduleRequestId + ")"); + + System.out.println("-----------------------------APPROVING LOAN RESCHEDULE REQUEST--------------------------"); + + requestJSON = new LoanRescheduleRequestTestBuilder().updateSubmittedOnDate(rescheduleSubmittedDate) + .getApproveLoanRescheduleRequestJSON(); + this.loanRescheduleRequestHelper.approveLoanRescheduleRequest(this.loanRescheduleRequestId, requestJSON); + + final HashMap response = (HashMap) this.loanRescheduleRequestHelper.getLoanRescheduleRequest(loanRescheduleRequestId, "statusEnum"); + assertTrue((Boolean) response.get("approved")); + + loanRepaymnetSchedule = this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, generalResponseSpec, this.loanId); + + dueDateLoanSchedule = (ArrayList) ((HashMap) loanRepaymnetSchedule.get(2)).get("dueDate"); + dueDateCalendar.set((Integer) dueDateLoanSchedule.get(0), (Integer) dueDateLoanSchedule.get(1) - 1, + (Integer) dueDateLoanSchedule.get(2)); + assertEquals("AFTER MEETING CHANGE DATE THE NEXT REPAYMENT SHOULD BE ON TUESDAY, EVEN AFTER RESCHEDULE", 3, + dueDateCalendar.get(Calendar.DAY_OF_WEEK)); + System.out.println("Successfully changed group meeting date (CAELNDAR ID: " + this.groupCalendarId + + ") and rescheduled loan (RESCHEDULE ID: " + this.loanRescheduleRequestId + ")"); + + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403); + } + + @Test + public void testChangeGroupMeetingMaturedOnDate() { + System.out + .println("---------------------------------STARTING GROUP LOAN MEETING CHANGE DATE EXPECTED MATURED CHANGE------------------------------------------"); + + Calendar meetingCalendar = Calendar.getInstance(); + meetingCalendar.setFirstDayOfWeek(Calendar.MONDAY); + meetingCalendar.setTime(new java.util.Date()); + + int today = meetingCalendar.get(Calendar.DAY_OF_WEEK); + // making sure that the meeting calendar is set for the coming monday. + if (today >= Calendar.MONDAY) { + meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today + 7)); + } else { + meetingCalendar.add(Calendar.DAY_OF_YEAR, +(Calendar.MONDAY - today)); + } + + Calendar groupMeetingChangeCalendar = (Calendar) meetingCalendar.clone(); + + meetingCalendar.add(Calendar.WEEK_OF_YEAR, -3); + + final String groupMeetingDate = this.dateFormatterMifosStandard.format(meetingCalendar.getTime()); + + final String disbursalDate = groupMeetingDate; // first meeting date + // after group creation + + final String loanType = "jlg"; + groupMeetingChangeCalendar.add(Calendar.DAY_OF_YEAR, 1); + final String groupMeetingNewStartDate = this.dateFormatterMifosStandard.format(groupMeetingChangeCalendar.getTime()); + // The date + // from + // which we + // start the + // new group + // meeting + // occasion, + // this is a + // tuesday. + + this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); + this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); + this.requestSpec.header("X-Mifos-Platform-TenantId", "default"); + this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); + this.generalResponseSpec = new ResponseSpecBuilder().build(); + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec); + + this.loanRescheduleRequestHelper = new LoanRescheduleRequestHelper(this.requestSpec, this.responseSpec); + System.out.println("---------------------------------CREATING ENTITIES AND JLG LOAN ------------------------------------------"); + // create all required entities + this.createRequiredEntitiesForJLGLoanSync(groupMeetingDate); + + final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount).withLoanTermFrequency("24") + .withLoanTermFrequencyAsWeeks().withNumberOfRepayments("12").withRepaymentEveryAfter("2") + .withRepaymentFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestCalculationPeriodTypeAsDays() + .withInterestRatePerPeriod(interestRatePerPeriod).withRepaymentFrequencyTypeAsWeeks().withSubmittedOnDate(disbursalDate) + .withExpectedDisbursementDate(disbursalDate).withLoanType(loanType).withSyncDisbursementWithMeetin() + .withCalendarID(this.groupCalendarId.toString()) + .build(this.clientId.toString(), this.groupId.toString(), this.loanProductId.toString(), null); + + this.loanId = this.loanTransactionHelper.getLoanId(loanApplicationJSON); + + // Test for loan account is created + Assert.assertNotNull(this.loanId); + HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + + LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap); + + // Test for loan account is created, can be approved + this.loanTransactionHelper.approveLoan(disbursalDate, this.loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap); + + // Test for loan account approved can be disbursed + this.loanTransactionHelper.disburseLoan(disbursalDate, this.loanId); + loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, this.loanId); + LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap); + + System.out.println("---------------------------------CHANGING GROUP MEETING DATE ------------------------------------------"); + CalendarHelper.updateMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, this.groupCalendarId.toString(), + groupMeetingNewStartDate, "2", "2", "2"); // New meeting dates + // will be the tuesday + // after the + // coming + // monday + + Calendar expectedMaturityCalendar = Calendar.getInstance(); + expectedMaturityCalendar.setFirstDayOfWeek(Calendar.MONDAY); + ArrayList expectedMaturityDate = ((ArrayList) ((HashMap) this.loanTransactionHelper.getLoanDetail(requestSpec, generalResponseSpec, + this.loanId, "timeline")).get("expectedMaturityDate")); + + expectedMaturityCalendar.set((Integer) expectedMaturityDate.get(0), (Integer) expectedMaturityDate.get(1) - 1, + (Integer) expectedMaturityDate.get(2)); + + assertEquals("AFTER MEETING CHANGE DATE THE EXPECTED MATURITY SHOULD BE ON TUESDAY", 3, + expectedMaturityCalendar.get(Calendar.DAY_OF_WEEK)); + + this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpecForStatusCode403); + } + + /** + * entities for jlg loan + **/ + private void createRequiredEntitiesForJLGLoanSync(final String groupActivationDate) { + this.createGroupEntityWithCalendar("2", "2", "1", groupActivationDate);// frequency=2:Weekly + // , interval=2: + // Every two weeks , + // repeatsOnDay=1:Monday + // groupActivationDate is decided by the current date + this.createClientEntity(); + this.associateClientToGroup(this.groupId, this.clientId); + this.createLoanProductEntity(); + + } + + /* + * Associate client to the group + */ + + private void associateClientToGroup(final Integer groupId, final Integer clientId) { + GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupId.toString(), clientId.toString()); + GroupHelper.verifyGroupMembers(this.requestSpec, this.responseSpec, groupId, clientId); + } + + private void createGroupEntityWithCalendar(final String frequency, final String interval, final String repeatsOnDay, + final String groupActivationDate) { + this.groupId = GroupHelper.createGroup(this.requestSpec, this.responseSpec, groupActivationDate); + GroupHelper.verifyGroupCreatedOnServer(this.requestSpec, this.responseSpec, this.groupId); + + final String startDate = groupActivationDate; + + this.setGroupCalendarId(CalendarHelper.createMeetingCalendarForGroup(this.requestSpec, this.responseSpec, this.groupId, startDate, + frequency, interval, repeatsOnDay)); + } + + /** + * create a new client + **/ + private void createClientEntity() { + this.clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, this.clientId); + } + + /** + * create a new loan product + **/ + private void createLoanProductEntity() { + final String loanProductJSON = new LoanProductTestBuilder().withPrincipal(loanPrincipalAmount) + .withNumberOfRepayments(numberOfRepayments).withinterestRatePerPeriod(interestRatePerPeriod) + .withInterestRateFrequencyTypeAsYear().build(null); + this.loanProductId = this.loanTransactionHelper.getLoanProductId(loanProductJSON); + } + + public void setGroupCalendarId(Integer groupCalendarId) { + this.groupCalendarId = groupCalendarId; + } +} \ No newline at end of file diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/CalendarHelper.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/CalendarHelper.java index 8f5c7be62..a18d9241b 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/CalendarHelper.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/CalendarHelper.java @@ -20,29 +20,43 @@ public class CalendarHelper { private static final String PARENT_ENTITY_NAME = "groups/"; private static final String ENITY_NAME = "/calendars"; - 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) { - + 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) { + System.out.println("---------------------------------CREATING A MEETING CALENDAR FOR THE GROUP------------------------------"); - - final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + groupId + ENITY_NAME + "?" + Utils.TENANT_IDENTIFIER; - + + final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + 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"); + + return Utils.performServerPost(requestSpec, responseSpec, CALENDAR_RESOURCE_URL, + getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate), "resourceId"); } - public static String getTestCalendarAsJSON(final String frequency, final String interval,final String repeatsOnDay, - final String startDate) { - + public static Integer updateMeetingCalendarForGroup(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, + final Integer groupId, String calendarID, final String startDate, final String frequency, final String interval, + final String repeatsOnDay) { + + System.out.println("---------------------------------UPDATING A MEETING CALENDAR FOR THE GROUP------------------------------"); + + final String CALENDAR_RESOURCE_URL = BASE_URL + PARENT_ENTITY_NAME + groupId + ENITY_NAME + "/" + calendarID; + + System.out.println(CALENDAR_RESOURCE_URL); + // TODO: check that resource id indeed exists in calendar update put. + return Utils.performServerPut(requestSpec, responseSpec, CALENDAR_RESOURCE_URL, + getTestCalendarAsJSON(frequency, interval, repeatsOnDay, startDate), "resourceId"); + } + + public static String getTestCalendarAsJSON(final String frequency, final String interval, final String repeatsOnDay, + final String startDate) { + final HashMap map = new HashMap<>(); map.put("dateFormat", "dd MMMM yyyy"); map.put("locale", "en"); map.put("frequency", frequency); map.put("interval", interval); map.put("repeating", "true"); - map.put("repeatsOnDay", repeatsOnDay ); + map.put("repeatsOnDay", repeatsOnDay); map.put("title", Utils.randomNameGenerator("groups_CollectionMeeting", 4)); map.put("typeId", "1"); map.put("startDate", startDate); @@ -54,7 +68,8 @@ public class CalendarHelper { final Integer generatedGroupId, final Integer generatedCalendarId) { System.out.println("------------------------------CHECK CALENDAR DETAILS------------------------------------\n"); final String CLIENT_URL = "/mifosng-provider/api/v1/groups/" + generatedGroupId + "?associations=all&" + Utils.TENANT_IDENTIFIER; - final String responseCalendarDetailsinJSON = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, "collectionMeetingCalendar"); + final String responseCalendarDetailsinJSON = Utils.performServerGet(requestSpec, responseSpec, CLIENT_URL, + "collectionMeetingCalendar"); final Integer responseCalendarId = from(responseCalendarDetailsinJSON).get("id"); assertEquals("ERROR IN CREATING THE CALENDAR", generatedCalendarId, responseCalendarId); } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java index 6c4e3fe96..6435f530d 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -50,16 +50,37 @@ public class LoanApplicationTestBuilder { private String recalculationCompoundingFrequencyDate = null; private String repaymentsStartingFromDate = null; + private String calendarId; + private boolean syncDisbursementWithMeeting = false; + + public String build(final String clientID, final String groupID, final String loanProductId, final String savingsID) { + final HashMap map = new HashMap<>(); + map.put("groupId", groupID); + map.put("clientId", clientID); + if (this.loanType == "jlg") { + if (this.calendarId != null) { + map.put("calendarId", this.calendarId); + } + map.put("syncDisbursementWithMeeting", this.syncDisbursementWithMeeting); + } + return build(map, loanProductId, savingsID); + } + public String build(final String ID, final String loanProductId, final String savingsID) { final HashMap map = new HashMap<>(); - map.put("dateFormat", "dd MMMM yyyy"); - map.put("locale", "en_GB"); + if (this.loanType == "group") { map.put("groupId", ID); } else { map.put("clientId", ID); } + return build(map, loanProductId, savingsID); + } + + private String build(final HashMap map, final String loanProductId, final String savingsID) { + map.put("dateFormat", "dd MMMM yyyy"); + map.put("locale", "en_GB"); map.put("productId", loanProductId); map.put("principal", this.principal); map.put("loanTermFrequency", this.loanTermFrequency); @@ -105,6 +126,7 @@ public class LoanApplicationTestBuilder { if (recalculationCompoundingFrequencyDate != null) { map.put("recalculationCompoundingFrequencyDate", recalculationCompoundingFrequencyDate); } + System.out.println("Loan Application request : " + map); return new Gson().toJson(map); } @@ -258,4 +280,32 @@ public class LoanApplicationTestBuilder { this.repaymentsStartingFromDate = firstRepaymentDate; return this; } + + /** + * calendarID parameter is used to sync repayments with group meetings, + * especially when using jlg loans + * + * @param calendarId + * the id of the calender record of the group meeting from + * m_calendar table + * @return + */ + public LoanApplicationTestBuilder withCalendarID(String calendarId) { + this.calendarId = calendarId; + return this; + } + + /** + * This indicator is used mainly for jlg loans when we want to sync + * disbursement with the group meetings (it seems that if we do use this + * parameter we should also use calendarID to sync repayment with group + * meetings) + * + * @return + */ + public LoanApplicationTestBuilder withSyncDisbursementWithMeetin() { + this.syncDisbursementWithMeeting = true; + return this; + } + } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java index a5e112b4e..73330c6d3 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanRescheduleRequestTestBuilder.java @@ -11,126 +11,133 @@ import java.util.HashMap; import com.google.gson.Gson; public class LoanRescheduleRequestTestBuilder { - private String rescheduleFromDate = "04 December 2014"; - private String graceOnPrincipal = "2"; - private String graceOnInterest = "2"; - private String extraTerms = "2"; - private Boolean recalculateInterest = false; - private String newInterestRate = null; - private String adjustedDueDate = null; - private String rescheduleReasonId = "1"; - private String rescheduleReasonComment = null; - private String submittedOnDate = "04 September 2014"; - - public String build(final String loanId) { - final HashMap map = new HashMap<>(); - - map.put("dateFormat", "dd MMMM yyyy"); + + private String rescheduleFromDate = "04 December 2014"; + private String graceOnPrincipal = "2"; + private String graceOnInterest = "2"; + private String extraTerms = "2"; + private Boolean recalculateInterest = false; + private String newInterestRate = null; + private String adjustedDueDate = null; + private String rescheduleReasonId = "1"; + private String rescheduleReasonComment = null; + private String submittedOnDate = "04 September 2014"; + + public String build(final String loanId) { + final HashMap map = new HashMap<>(); + + map.put("dateFormat", "dd MMMM yyyy"); map.put("locale", "en_GB"); map.put("loanId", loanId); map.put("submittedOnDate", submittedOnDate); - map.put("rescheduleFromDate", rescheduleFromDate); - - if(graceOnPrincipal != null) { - map.put("graceOnPrincipal", graceOnPrincipal); - } - - if(graceOnInterest != null) { - map.put("graceOnInterest", graceOnInterest); - } - - if(extraTerms != null) { - map.put("extraTerms", extraTerms); - } - - map.put("recalculateInterest", recalculateInterest); - - if(newInterestRate != null) { - map.put("newInterestRate", newInterestRate); - } - - if(adjustedDueDate != null) { - map.put("adjustedDueDate", adjustedDueDate); - } - - map.put("rescheduleReasonId", rescheduleReasonId); - - if(rescheduleReasonComment != null) { - map.put("rescheduleReasonComment", rescheduleReasonComment); - } - - return new Gson().toJson(map); - } - - public LoanRescheduleRequestTestBuilder updateRescheduleFromDate(final String rescheduleFromDate) { - if(rescheduleFromDate != null) { - this.rescheduleFromDate = rescheduleFromDate; - } - - return this; - } - - public LoanRescheduleRequestTestBuilder updateGraceOnPrincipal(final String graceOnPrincipal) { - this.graceOnPrincipal = graceOnPrincipal; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateGraceOnInterest(final String graceOnInterest) { - this.graceOnInterest = graceOnInterest; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateExtraTerms(final String extraTerms) { - this.extraTerms = extraTerms; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateRecalculateInterest(final Boolean recalculateInterest) { - this.recalculateInterest = recalculateInterest; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateNewInterestRate(final String newInterestRate) { - this.newInterestRate = newInterestRate; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateAdjustedDueDate(final String adjustedDueDate) { - this.adjustedDueDate = adjustedDueDate; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateRescheduleReasonId(final String rescheduleReasonId) { - this.rescheduleReasonId = rescheduleReasonId; - - return this; - } - - public LoanRescheduleRequestTestBuilder updateRescheduleReasonComment(final String rescheduleReasonComment) { - this.rescheduleReasonComment = rescheduleReasonComment; - - return this; - } - - public String getRejectLoanRescheduleRequestJSON() { - final HashMap map = new HashMap<>(); + map.put("rescheduleFromDate", rescheduleFromDate); + + if (graceOnPrincipal != null) { + map.put("graceOnPrincipal", graceOnPrincipal); + } + + if (graceOnInterest != null) { + map.put("graceOnInterest", graceOnInterest); + } + + if (extraTerms != null) { + map.put("extraTerms", extraTerms); + } + + map.put("recalculateInterest", recalculateInterest); + + if (newInterestRate != null) { + map.put("newInterestRate", newInterestRate); + } + + if (adjustedDueDate != null) { + map.put("adjustedDueDate", adjustedDueDate); + } + + map.put("rescheduleReasonId", rescheduleReasonId); + + if (rescheduleReasonComment != null) { + map.put("rescheduleReasonComment", rescheduleReasonComment); + } + + return new Gson().toJson(map); + } + + public LoanRescheduleRequestTestBuilder updateRescheduleFromDate(final String rescheduleFromDate) { + if (rescheduleFromDate != null) { + this.rescheduleFromDate = rescheduleFromDate; + } + + return this; + } + + public LoanRescheduleRequestTestBuilder updateGraceOnPrincipal(final String graceOnPrincipal) { + this.graceOnPrincipal = graceOnPrincipal; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateGraceOnInterest(final String graceOnInterest) { + this.graceOnInterest = graceOnInterest; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateExtraTerms(final String extraTerms) { + this.extraTerms = extraTerms; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateRecalculateInterest(final Boolean recalculateInterest) { + this.recalculateInterest = recalculateInterest; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateNewInterestRate(final String newInterestRate) { + this.newInterestRate = newInterestRate; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateAdjustedDueDate(final String adjustedDueDate) { + this.adjustedDueDate = adjustedDueDate; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateRescheduleReasonId(final String rescheduleReasonId) { + this.rescheduleReasonId = rescheduleReasonId; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateRescheduleReasonComment(final String rescheduleReasonComment) { + this.rescheduleReasonComment = rescheduleReasonComment; + + return this; + } + + public LoanRescheduleRequestTestBuilder updateSubmittedOnDate(final String submittedOnDate) { + this.submittedOnDate = submittedOnDate; + + return this; + } + + public String getRejectLoanRescheduleRequestJSON() { + final HashMap map = new HashMap<>(); map.put("locale", "en"); map.put("dateFormat", "dd MMMM yyyy"); map.put("rejectedOnDate", submittedOnDate); return new Gson().toJson(map); - } - - public String getApproveLoanRescheduleRequestJSON() { - final HashMap map = new HashMap<>(); + } + + public String getApproveLoanRescheduleRequestJSON() { + final HashMap map = new HashMap<>(); map.put("locale", "en"); map.put("dateFormat", "dd MMMM yyyy"); map.put("approvedOnDate", submittedOnDate); return new Gson().toJson(map); - } + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 29098e44d..ccaeb06a7 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -8,33 +8,9 @@ package org.mifosplatform.portfolio.loanaccount.domain; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.OrderBy; -import javax.persistence.Table; -import javax.persistence.Temporal; -import javax.persistence.TemporalType; -import javax.persistence.Transient; -import javax.persistence.UniqueConstraint; -import javax.persistence.Version; +import javax.persistence.*; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; @@ -82,32 +58,10 @@ import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanTermVariationsData; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; -import org.mifosplatform.portfolio.loanaccount.exception.ExceedingTrancheCountException; -import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanStateTransitionException; -import org.mifosplatform.portfolio.loanaccount.exception.InvalidLoanTransactionTypeException; -import org.mifosplatform.portfolio.loanaccount.exception.InvalidRefundDateException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanApplicationDateException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanDisbursalException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentDateException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerAssignmentException; -import org.mifosplatform.portfolio.loanaccount.exception.LoanOfficerUnassignmentDateException; -import org.mifosplatform.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; -import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; +import org.mifosplatform.portfolio.loanaccount.exception.*; +import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.*; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; -import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; -import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; -import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; -import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; -import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; -import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; +import org.mifosplatform.portfolio.loanproduct.domain.*; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.useradministration.domain.AppUser; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -1320,7 +1274,9 @@ public class Loan extends AbstractPersistable { } } - // the comparison should be done with the tenant date (DateUtils.getLocalDateOfTenant()) and not the server date (new LocalDate()) + // the comparison should be done with the tenant date + // (DateUtils.getLocalDateOfTenant()) and not the server date (new + // LocalDate()) if (getSubmittedOnDate().isAfter(DateUtils.getLocalDateOfTenant())) { final String errorMessage = "The date on which a loan is submitted cannot be in the future."; throw new InvalidLoanStateTransitionException("submittal", "cannot.be.a.future.date", errorMessage, getSubmittedOnDate()); @@ -2204,7 +2160,7 @@ public class Loan extends AbstractPersistable { BigDecimal setPrincipalAmount = BigDecimal.ZERO; BigDecimal totalAmount = BigDecimal.ZERO; for (LoanDisbursementDetails disbursementDetails : loanDisburseDetails) { - if(disbursementDetails.actualDisbursementDate() != null){ + if (disbursementDetails.actualDisbursementDate() != null) { setPrincipalAmount = setPrincipalAmount.add(disbursementDetails.principal()); } totalAmount = totalAmount.add(disbursementDetails.principal()); @@ -3959,7 +3915,7 @@ public class Loan extends AbstractPersistable { LocalDate newRepaymentDate = null; Boolean isFirstTime = true; - + LocalDate latestRepaymentDate = null; for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); @@ -3982,7 +3938,9 @@ public class Loan extends AbstractPersistable { if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } - + if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) { + latestRepaymentDate = newRepaymentDate; + } loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); // reset from date to get actual daysInPeriod @@ -3996,6 +3954,9 @@ public class Loan extends AbstractPersistable { tmpFromDate = oldDueDate; } } + if (latestRepaymentDate != null) { + this.expectedMaturityDate = latestRepaymentDate.toDate(); + } } public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, @@ -4009,7 +3970,7 @@ public class Loan extends AbstractPersistable { LocalDate newRepaymentDate = null; LocalDate seedDate = meetingStartDate; - + LocalDate latestRepaymentDate = null; for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); @@ -4032,6 +3993,9 @@ public class Loan extends AbstractPersistable { if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } + if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) { + latestRepaymentDate = newRepaymentDate; + } loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); // reset from date to get actual daysInPeriod @@ -4042,6 +4006,9 @@ public class Loan extends AbstractPersistable { tmpFromDate = oldDueDate; } } + if (latestRepaymentDate != null) { + this.expectedMaturityDate = latestRepaymentDate.toDate(); + } } private LocalDate getMaxDateLimitForNewRepayment(final PeriodFrequencyType periodFrequencyType, final Integer loanRepaymentInterval, @@ -4929,8 +4896,6 @@ public class Loan extends AbstractPersistable { } /** - * @param loan - * the Loan object * @return loan disbursement data **/ public List getDisbursmentData() { @@ -4963,13 +4928,13 @@ public class Loan extends AbstractPersistable { * TODO * @param compoundingCalendarInstance * TODO - * @param loan - * the Loan object + * @param loanCalendarInstance + * Used for accessing the loan's calendar object * @return application terms of the Loan object **/ @SuppressWarnings({ "unused" }) public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, - final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance) { + final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -5003,7 +4968,12 @@ public class Loan extends AbstractPersistable { // TODO get calender linked to loan if any exist. As of 17-07-2014, // could not find a link in the database. // skip for now and set the Calender object to null - Calendar calendar = null; + // Calendar loanCalendar = null; + // The calendar instance might be null if the loan is not connected + // To a calendar object + // if (loanCalendarInstance != null) { + // loanCalendar = loanCalendarInstance.getCalendar(); + // } final Integer graceOnPrincipalPayment = this.loanRepaymentScheduleDetail.graceOnPrincipalPayment(); final Integer graceOnInterestPayment = this.loanRepaymentScheduleDetail.graceOnInterestPayment(); @@ -5039,7 +5009,7 @@ public class Loan extends AbstractPersistable { maxOutstandingBalance, loanVariationTermsData, interestChargedFromDate, this.loanProduct.getPrincipalThresholdForLastInstallment(), this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, - this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod); + this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar); } /** diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java index e6ba38bbb..068dca803 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/AbstractLoanScheduleGenerator.java @@ -7,15 +7,7 @@ package org.mifosplatform.portfolio.loanaccount.loanschedule.domain; import java.math.BigDecimal; import java.math.MathContext; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; +import java.util.*; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -24,16 +16,13 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; -import org.mifosplatform.portfolio.loanaccount.domain.Loan; -import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary; -import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; +import org.mifosplatform.portfolio.loanaccount.domain.*; import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException; import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException; @@ -101,7 +90,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener LocalDate actualRepaymentDate = periodStartDate; boolean isFirstRepayment = true; LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate, loanApplicationTerms, - isFirstRepayment); + isFirstRepayment, holidayDetailDTO); final LocalDate idealDisbursementDate = this.scheduledDateGenerator.idealDisbursementDateBasedOnFirstRepaymentDate( loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); @@ -115,7 +104,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener Money outstandingBalance = principalDisbursed; // Set fixed EMI Amount - updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance); + updateFixedInstallmentAmount(mc, loanApplicationTerms, actualRepaymentDate, periodNumber, outstandingBalance, holidayDetailDTO); // disbursement map for tranche details(will added to outstanding // balance as per the start date) @@ -173,7 +162,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isFirstRepayment); + isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); @@ -372,7 +361,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // method applies early payment strategy reducePrincipal = reducePrincipal.plus(unprocessed); reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, - totalCumulativePrincipal, periodNumber, mc); + totalCumulativePrincipal, periodNumber, mc, holidayDetailDTO); } // identify late payments and add compounding // details to @@ -524,7 +513,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener reducePrincipal = reducePrincipal.plus(earlyPaidAmount); BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, totalCumulativePrincipal, - periodNumber + 1, mc); + periodNumber + 1, mc, holidayDetailDTO); if (fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) { isEmiAmountChanged = true; } @@ -597,7 +586,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (isEmiAmountChanged) { updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), periodNumber, loanApplicationTerms - .getPrincipal().minus(totalCumulativePrincipal)); + .getPrincipal().minus(totalCumulativePrincipal), holidayDetailDTO); } PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction, @@ -642,7 +631,8 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal)); BigDecimal fixedEmiAmount = loanApplicationTerms.getFixedEmiAmount(); reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal, - totalCumulativePrincipal.plus(principalForThisPeriod.minus(principalProcessed)), periodNumber + 1, mc); + totalCumulativePrincipal.plus(principalForThisPeriod.minus(principalProcessed)), periodNumber + 1, mc, + holidayDetailDTO); if (fixedEmiAmount.compareTo(loanApplicationTerms.getFixedEmiAmount()) != 0) { isEmiAmountChanged = true; } @@ -736,12 +726,12 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener } private boolean updateFixedInstallmentAmount(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, - LocalDate actualRepaymentDate, int periodNumber, Money outstandingBalance) { + LocalDate actualRepaymentDate, int periodNumber, Money outstandingBalance, final HolidayDetailDTO holidayDetailDTO) { boolean isAmountChanged = false; if (loanApplicationTerms.getActualFixedEmiAmount() == null && loanApplicationTerms.getInterestMethod().isDecliningBalnce() && loanApplicationTerms.getAmortizationMethod().isEqualInstallment()) { LocalDate nextPeriodDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, - false); + false, holidayDetailDTO); if (periodNumber < loanApplicationTerms.getPrincipalGrace() + 1) { periodNumber = loanApplicationTerms.getPrincipalGrace() + 1; } @@ -803,7 +793,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, - isFirstRepayment); + isFirstRepayment, holidayDetailDTO); int daysInPeriod = Days.daysBetween(periodStartDate, actualRepaymentDate).getDays(); if (actualRepaymentDate.isAfter(currentDate)) { actualRepaymentDate = currentDate; @@ -932,7 +922,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener * TODO */ private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms, Money reducePrincipal, - final Money totalCumulativePrincipal, int periodNumber, final MathContext mc) { + final Money totalCumulativePrincipal, int periodNumber, final MathContext mc, final HolidayDetailDTO holidayDetailDTO) { if (reducePrincipal.isGreaterThanZero()) { switch (loanApplicationTerms.getRescheduleStrategyMethod()) { case REDUCE_EMI_AMOUNT: @@ -941,7 +931,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener if (loanApplicationTerms.getActualFixedEmiAmount() == null) { loanApplicationTerms.setFixedEmiAmount(null); updateFixedInstallmentAmount(mc, loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), - periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal)); + periodNumber, loanApplicationTerms.getPrincipal().minus(totalCumulativePrincipal), holidayDetailDTO); } reducePrincipal = reducePrincipal.zero(); break; @@ -1272,7 +1262,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener @Override public LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, - final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance) { + final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { final Loan loan = loanRescheduleRequest.getLoan(); final LoanSummary loanSummary = loan.getSummary(); @@ -1414,7 +1404,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener // get the loan application terms from the Loan object final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency, restCalendarInstance, - compoundingCalendarInstance); + compoundingCalendarInstance, loanCalendar); // update the number of repayments loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments); @@ -1454,7 +1444,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener || period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate()) || period.isNew()) { installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate, loanApplicationTerms, - false); + false, holidayDetailDTO); if (adjustedDueDate != null && periodNumber == 1) { installmentDueDate = adjustedDueDate; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index caf5f793f..5ab18d747 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -13,10 +13,16 @@ import org.joda.time.Years; import org.mifosplatform.organisation.holiday.service.HolidayUtil; import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType; import org.mifosplatform.organisation.workingdays.service.WorkingDaysUtil; +import org.mifosplatform.portfolio.calendar.domain.Calendar; +import org.mifosplatform.portfolio.calendar.domain.CalendarHistory; +import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; +import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.common.domain.DayOfWeekType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; +import java.util.Set; + public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override @@ -27,7 +33,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { LocalDate lastRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); boolean isFirstRepayment = true; for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) { - lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment); + lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; } lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO); @@ -36,15 +42,29 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { @Override public LocalDate generateNextRepaymentDate(final LocalDate lastRepaymentDate, final LoanApplicationTerms loanApplicationTerms, - boolean isFirstRepayment) { + boolean isFirstRepayment, final HolidayDetailDTO holidayDetailDTO) { final LocalDate firstRepaymentPeriodDate = loanApplicationTerms.getCalculatedRepaymentsStartingFromLocalDate(); LocalDate dueRepaymentPeriodDate = null; if (isFirstRepayment && firstRepaymentPeriodDate != null) { dueRepaymentPeriodDate = firstRepaymentPeriodDate; } else { + Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); + dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(), loanApplicationTerms.getWeekDayType()); + if (currentCalendar != null) { + // If we have currentCalendar object, this neans there is a + // 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, + loanApplicationTerms.getRepaymentEvery(), + CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(loanApplicationTerms.getLoanTermPeriodFrequencyType()), + holidayDetailDTO.getWorkingDays()); + } } return dueRepaymentPeriodDate; } @@ -54,19 +74,20 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { final HolidayDetailDTO holidayDetailDTO) { LocalDate adjustedDate = dueRepaymentPeriodDate; - + LocalDate nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), adjustedDate, loanApplicationTerms.getNthDay(), loanApplicationTerms.getWeekDayType()); - - final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays().getRepaymentReschedulingType()); + + final RepaymentRescheduleType rescheduleType = RepaymentRescheduleType.fromInt(holidayDetailDTO.getWorkingDays() + .getRepaymentReschedulingType()); /** * Fix for https://mifosforge.jira.com/browse/MIFOSX-1357 */ // recursively check for the next working meeting day. - while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate) && - rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) { + while (WorkingDaysUtil.isNonWorkingDay(holidayDetailDTO.getWorkingDays(), nextDueRepaymentPeriodDate) + && rescheduleType == RepaymentRescheduleType.MOVE_TO_NEXT_REPAYMENT_MEETING_DAY) { nextDueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), nextDueRepaymentPeriodDate, loanApplicationTerms.getNthDay(), @@ -196,7 +217,7 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { LocalDate generatedDate = loanApplicationTerms.getExpectedDisbursementDate(); boolean isFirstRepayment = true; while (!generatedDate.isAfter(lastRepaymentDate)) { - generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment); + generatedDate = generateNextRepaymentDate(generatedDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; } generatedDate = adjustRepaymentDate(generatedDate, loanApplicationTerms, holidayDetailDTO); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index c70437be4..145e61cf1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -18,6 +18,7 @@ import org.joda.time.PeriodType; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.common.domain.DayOfWeekType; import org.mifosplatform.portfolio.common.domain.DaysInMonthType; @@ -39,6 +40,8 @@ import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType public final class LoanApplicationTerms { private final ApplicationCurrency currency; + + private final Calendar loanCalendar; private Integer loanTermFrequency; private final PeriodFrequencyType loanTermPeriodFrequencyType; private Integer numberOfRepayments; @@ -160,7 +163,7 @@ public final class LoanApplicationTerms { final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy) { + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) { final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; @@ -172,7 +175,7 @@ public final class LoanApplicationTerms { disbursementDatas, maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy); + installmentAmountInMultiplesOf, preClosureInterestCalculationStrategy, loanCalendar); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -188,6 +191,29 @@ public final class LoanApplicationTerms { final RecalculationFrequencyType compoundingFrequencyType, final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final LoanRescheduleStrategyMethod rescheduleStrategyMethod) { + final Calendar loanCalendar = null; + + return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, + expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance, + loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, + interestChargedFromDate, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, + restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, + loanPreClosureInterestCalculationStrategy, rescheduleStrategyMethod, loanCalendar); + } + + public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, NthDayType nthDay, DayOfWeekType dayOfWeek, + final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, final Money inArrearsTolerance, + final LoanProductRelatedDetail loanProductRelatedDetail, final boolean multiDisburseLoan, final BigDecimal emiAmount, + final List disbursementDatas, final BigDecimal maxOutstandingBalance, + final List emiAmountVariations, final LocalDate interestChargedFromDate, + final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, + final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, + final InterestRecalculationCompoundingMethod compoundingMethod, final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, final Calendar loanCalendar) { final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); @@ -210,7 +236,6 @@ public final class LoanApplicationTerms { final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay.getValue(), dayOfWeek, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, @@ -220,7 +245,7 @@ public final class LoanApplicationTerms { loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy); + loanPreClosureInterestCalculationStrategy, loanCalendar); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -262,7 +287,7 @@ public final class LoanApplicationTerms { rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod(); interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod(); } - + final Calendar loanCalendar = null; return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, @@ -272,7 +297,7 @@ public final class LoanApplicationTerms { daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy); + loanPreClosureInterestCalculationStrategy, loanCalendar); } public static LoanApplicationTerms assembleFrom(final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, @@ -292,7 +317,8 @@ public final class LoanApplicationTerms { applicationTerms.interestRecalculationCompoundingMethod, applicationTerms.restCalendarInstance, applicationTerms.recalculationFrequencyType, applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType, applicationTerms.principalThresholdForLastInstalment, - applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy); + applicationTerms.installmentAmountInMultiplesOf, applicationTerms.preClosureInterestCalculationStrategy, + applicationTerms.loanCalendar); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -312,7 +338,7 @@ public final class LoanApplicationTerms { final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy) { + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -359,6 +385,8 @@ public final class LoanApplicationTerms { this.principalThresholdForLastInstalment = principalThresholdForLastInstalment; this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy; + + this.loanCalendar = loanCalendar; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -1211,8 +1239,11 @@ public final class LoanApplicationTerms { return this.compoundingFrequencyType; } - public BigDecimal getActualFixedEmiAmount() { return this.actualFixedEmiAmount; } + + public Calendar getLoanCalendar() { + return loanCalendar; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java index 85425d10c..7ebcbb0b5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanScheduleGenerator.java @@ -12,6 +12,7 @@ import java.util.Set; import org.joda.time.LocalDate; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; @@ -32,8 +33,10 @@ public interface LoanScheduleGenerator { LoanRepaymentScheduleInstallment calculatePrepaymentAmount(List installments, MonetaryCurrency currency, LocalDate onDate, LoanApplicationTerms loanApplicationTerms, MathContext mc, - Set charges, HolidayDetailDTO holidayDetailDTO, List loanTransactions, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); + Set charges, HolidayDetailDTO holidayDetailDTO, List loanTransactions, + LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor); LoanRescheduleModel reschedule(final MathContext mathContext, final LoanRescheduleRequest loanRescheduleRequest, - final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance); + final ApplicationCurrency applicationCurrency, final HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, + CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar); } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java index 078bf3054..d97cff7a5 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/ScheduledDateGenerator.java @@ -17,7 +17,8 @@ public interface ScheduledDateGenerator { LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery, final LocalDate firstRepaymentDate); - LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment); + LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment, + final HolidayDetailDTO holidayDetailDTO); LocalDate adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index b01769b4a..60afc4008 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -302,7 +302,7 @@ public class LoanScheduleAssembler { loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, - installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy()); + installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar); } private CalendarInstance createInterestRecalculationCalendarInstance(final LocalDate calendarStartDate, diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java index 1fff0131f..be00e9f85 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/DefaultLoanReschedulerFactory.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator; @@ -20,19 +21,19 @@ 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 CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar) { LoanRescheduleModel loanRescheduleModel = null; switch (interestMethod) { case DECLINING_BALANCE: loanRescheduleModel = new DecliningBalanceInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); break; case FLAT: loanRescheduleModel = new FlatInterestLoanScheduleGenerator().reschedule(mathContext, loanRescheduleRequest, - applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, loanCalendar); break; case INVALID: diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java index dd4d8a051..af09529d8 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/domain/LoanReschedulerFactory.java @@ -8,6 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain; import java.math.MathContext; import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -16,5 +17,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 HolidayDetailDTO holidayDetailDTO, CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, + final Calendar loanCalendar); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java index f3bb8c58a..c7d1a9401 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanReschedulePreviewPlatformServiceImpl.java @@ -18,6 +18,7 @@ import org.mifosplatform.organisation.monetary.domain.ApplicationCurrencyReposit import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; @@ -92,9 +93,15 @@ public class LoanReschedulePreviewPlatformServiceImpl implements LoanRescheduleP compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } - + final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + Calendar loanCalendar = null; + if (loanCalendarInstance != null) { + loanCalendar = loanCalendarInstance.getCalendar(); + } LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + loanCalendar); LoanRescheduleModel loanRescheduleModelWithOldPeriods = LoanRescheduleModel.createWithSchedulehistory(loanRescheduleModel, oldPeriods); return loanRescheduleModelWithOldPeriods; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index ff0aa8ffc..6efbff044 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -8,13 +8,7 @@ package org.mifosplatform.portfolio.loanaccount.rescheduleloan.service; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; @@ -36,38 +30,21 @@ import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; import org.mifosplatform.organisation.monetary.domain.Money; import org.mifosplatform.organisation.workingdays.domain.WorkingDays; import org.mifosplatform.organisation.workingdays.domain.WorkingDaysRepositoryWrapper; +import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO; import org.mifosplatform.portfolio.loanaccount.data.LoanChargePaidByData; import org.mifosplatform.portfolio.loanaccount.data.ScheduleGeneratorDTO; -import org.mifosplatform.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; -import org.mifosplatform.portfolio.loanaccount.domain.Loan; -import org.mifosplatform.portfolio.loanaccount.domain.LoanAccountDomainService; -import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge; -import org.mifosplatform.portfolio.loanaccount.domain.LoanInstallmentCharge; -import org.mifosplatform.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepository; -import org.mifosplatform.portfolio.loanaccount.domain.LoanRepositoryWrapper; -import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; -import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary; -import org.mifosplatform.portfolio.loanaccount.domain.LoanSummaryWrapper; -import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction; -import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionRepository; -import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; +import org.mifosplatform.portfolio.loanaccount.domain.*; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistory; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanRepaymentScheduleHistoryRepository; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory; import org.mifosplatform.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator; -import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.DefaultLoanReschedulerFactory; -import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel; -import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod; -import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; -import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; +import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.*; import org.mifosplatform.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; import org.mifosplatform.portfolio.loanaccount.service.LoanAssembler; import org.mifosplatform.portfolio.loanaccount.service.LoanChargeReadPlatformService; @@ -329,8 +306,15 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche compoundingCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId( loan.loanInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_COMPOUNDING_DETAIL.getValue()); } + final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstaneByEntityId(loan.getId(), + CalendarEntityType.LOANS.getValue()); + Calendar loanCalendar = null; + if (loanCalendarInstance != null) { + loanCalendar = loanCalendarInstance.getCalendar(); + } LoanRescheduleModel loanRescheduleModel = new DefaultLoanReschedulerFactory().reschedule(mathContext, interestMethod, - loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance); + loanRescheduleRequest, applicationCurrency, holidayDetailDTO, restCalendarInstance, compoundingCalendarInstance, + loanCalendar); final Collection periods = loanRescheduleModel.getPeriods(); List repaymentScheduleInstallments = loan.getRepaymentScheduleInstallments(); @@ -566,9 +550,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche /** * handles the data integrity violation exception for loan reschedule write * services - * - * @param jsonCommand - * JSON command object + * * @param dve * data integrity violation exception * @return void From 95c43200d6413e8d95f63aa53624a8707d14c514 Mon Sep 17 00:00:00 2001 From: pramod Date: Mon, 17 Aug 2015 13:27:22 +0530 Subject: [PATCH 2/2] cleaning up MIFOSX-1765 --- .../domain/DefaultScheduledDateGenerator.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java index 5ab18d747..fbfc4ddce 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/DefaultScheduledDateGenerator.java @@ -49,11 +49,11 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator { dueRepaymentPeriodDate = firstRepaymentPeriodDate; } else { Calendar currentCalendar = loanApplicationTerms.getLoanCalendar(); - - dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), - loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(), - loanApplicationTerms.getWeekDayType()); - if (currentCalendar != null) { + if (currentCalendar == null) { + dueRepaymentPeriodDate = getRepaymentPeriodDate(loanApplicationTerms.getRepaymentPeriodFrequencyType(), + loanApplicationTerms.getRepaymentEvery(), lastRepaymentDate, loanApplicationTerms.getNthDay(), + loanApplicationTerms.getWeekDayType()); + } else { // If we have currentCalendar object, this neans there is a // calendar associated with // the loan, and we should use it in order to calculate next