http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java index a525606..84c53b1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/calendar/service/CalendarUtils.java @@ -30,6 +30,7 @@ import java.util.StringTokenizer; import net.fortuna.ical4j.model.Date; import net.fortuna.ical4j.model.DateList; import net.fortuna.ical4j.model.DateTime; +import net.fortuna.ical4j.model.NumberList; import net.fortuna.ical4j.model.Recur; import net.fortuna.ical4j.model.ValidationException; import net.fortuna.ical4j.model.WeekDay; @@ -37,18 +38,23 @@ import net.fortuna.ical4j.model.WeekDayList; import net.fortuna.ical4j.model.parameter.Value; import net.fortuna.ical4j.model.property.RRule; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.organisation.workingdays.domain.WorkingDays; import org.apache.fineract.organisation.workingdays.service.WorkingDaysUtil; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarFrequencyType; import org.apache.fineract.portfolio.calendar.domain.CalendarWeekDaysType; +import org.apache.fineract.portfolio.common.domain.NthDayType; import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.joda.time.LocalDate; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; +import com.google.gson.JsonElement; + public class CalendarUtils { static { @@ -260,10 +266,48 @@ public class CalendarUtils { } } else if (recur.getFrequency().equals(Recur.MONTHLY)) { - if (recur.getInterval() == 1) { + NumberList nthDays = recur.getSetPosList(); + Integer nthDay = null; + if (!nthDays.isEmpty()) + nthDay = (Integer) nthDays.get(0); + NumberList monthDays = recur.getMonthDayList(); + Integer monthDay = null; + if (!monthDays.isEmpty()) + monthDay = (Integer) monthDays.get(0); + WeekDayList weekdays = recur.getDayList(); + WeekDay weekDay = null; + if (!weekdays.isEmpty()) + weekDay = (WeekDay) weekdays.get(0); + if (nthDay != null && weekDay != null) { + NthDayType nthDayType = NthDayType.fromInt(nthDay); + NthDayNameEnum nthDayName = NthDayNameEnum.from(nthDayType.toString()); + DayNameEnum weekdayType = DayNameEnum.from(weekDay.getDay()); + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on " + nthDayName.getCode().toLowerCase() + " " + weekdayType.getCode().toLowerCase(); + } else { + humanReadable = "Every " + recur.getInterval() + " months on " + nthDayName.getCode().toLowerCase() + " " + + weekdayType.getCode().toLowerCase(); + } + } else if (monthDay != null) { + if (monthDay == -1) { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on last day"; + } else { + humanReadable = "Every " + recur.getInterval() + " months on last day"; + } + } else { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { + humanReadable = "Monthly on day " + monthDay; + } else { + humanReadable = "Every " + recur.getInterval() + " months on day " + monthDay; + } + } + } else { + if (recur.getInterval() == 1 || recur.getInterval() == -1) { humanReadable = "Monthly on day " + startDate.getDayOfMonth(); } else { humanReadable = "Every " + recur.getInterval() + " months on day " + startDate.getDayOfMonth(); + } } } else if (recur.getFrequency().equals(Recur.YEARLY)) { if (recur.getInterval() == 1) { @@ -347,6 +391,28 @@ public class CalendarUtils { return DayNameEnum.MO;// Default it to Monday } } + public static enum NthDayNameEnum { + ONE(1, "First"), TWO(2, "Second"), THREE(3, "Third"), FOUR(4, "Fourth"), FIVE(5, "Fifth"), LAST(-1, "Last"), INVALID(0, "Invalid"); + private final String code; + private final Integer value; + + private NthDayNameEnum(final Integer value, final String code) { + this.value = value; + this.code = code; + } + public String getCode() { + return this.code; + } + public int getValue() { + return this.value; + } + public static NthDayNameEnum from(final String name) { + for (final NthDayNameEnum nthDayName : NthDayNameEnum.values()) { + if (nthDayName.toString().equals(name)) { return nthDayName; } + } + return NthDayNameEnum.INVALID; + } + } public static PeriodFrequencyType getMeetingPeriodFrequencyType(final String recurringRule) { final Recur recur = CalendarUtils.getICalRecur(recurringRule); @@ -399,6 +465,18 @@ public class CalendarUtils { WeekDay weekDay = (WeekDay) weekDays.get(0); return CalendarWeekDaysType.fromString(weekDay.getDay()); } + public static NthDayType getRepeatsOnNthDayOfMonth(final String recurringRule) { + final Recur recur = CalendarUtils.getICalRecur(recurringRule); + NumberList monthDays = null; + if(recur.getDayList().isEmpty()) + monthDays = recur.getMonthDayList(); + else + monthDays = recur.getSetPosList(); + if (monthDays.isEmpty()) return NthDayType.INVALID; + if (!recur.getMonthDayList().isEmpty() && recur.getSetPosList().isEmpty()) return NthDayType.ONDAY; + Integer monthDay = (Integer) monthDays.get(0); + return NthDayType.fromInt(monthDay); + } public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, final LocalDate disbursementDate, final Integer loanRepaymentInterval, final String frequency, boolean isSkipRepaymentOnFirstDayOfMonth, @@ -589,4 +667,37 @@ public class CalendarUtils { return scheduleDate; } + public static void validateNthDayOfMonthFrequency(DataValidatorBuilder baseDataValidator, final String repeatsOnNthDayOfMonthParamName, + final String repeatsOnDayParamName, final JsonElement element, final FromJsonHelper fromApiJsonHelper) { + final Integer repeatsOnNthDayOfMonth = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnNthDayOfMonthParamName, element); + baseDataValidator.reset().parameter(repeatsOnNthDayOfMonthParamName).value(repeatsOnNthDayOfMonth).ignoreIfNull() + .isOneOfTheseValues(NthDayType.ONE.getValue(), NthDayType.TWO.getValue(), NthDayType.THREE.getValue(), + NthDayType.FOUR.getValue(), NthDayType.LAST.getValue(), NthDayType.ONDAY.getValue()); + final Integer repeatsOnDay = fromApiJsonHelper.extractIntegerSansLocaleNamed(repeatsOnDayParamName, element); + baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).ignoreIfNull() + .inMinMaxRange(CalendarWeekDaysType.getMinValue(), CalendarWeekDaysType.getMaxValue()); + NthDayType nthDayType = null; + if (repeatsOnNthDayOfMonth != null) { + nthDayType = NthDayType.fromInt(repeatsOnNthDayOfMonth); + } + if (nthDayType != null && nthDayType != NthDayType.INVALID) { + if (nthDayType == NthDayType.ONE || nthDayType == NthDayType.TWO || nthDayType == NthDayType.THREE + || nthDayType == NthDayType.FOUR) { + baseDataValidator.reset().parameter(repeatsOnDayParamName).value(repeatsOnDay).cantBeBlankWhenParameterProvidedIs( + repeatsOnNthDayOfMonthParamName, NthDayNameEnum.from(nthDayType.toString()).getCode().toLowerCase()); + } + } + } + public static Integer getMonthOnDay(String recurringRule) { + final Recur recur = CalendarUtils.getICalRecur(recurringRule); + NumberList monthDayList = null; + Integer monthOnDay = null; + if (getMeetingPeriodFrequencyType(recur).isMonthly()) { + monthDayList = recur.getMonthDayList(); + if (!monthDayList.isEmpty()) { + monthOnDay = (Integer) monthDayList.get(0); + } + } + return monthOnDay; + } }
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/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 c987bd7..be01e2b 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 @@ -33,7 +33,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; -import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.portfolio.calendar.CalendarConstants.CALENDAR_SUPPORTED_PARAMETERS; import org.apache.fineract.portfolio.calendar.domain.Calendar; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; @@ -291,8 +290,10 @@ public class CalendarWritePlatformServiceJpaRepositoryImpl implements CalendarWr if (reschedulebasedOnMeetingDates == null){ presentMeetingDate = command.localDateValueOfParameterNamed(CALENDAR_SUPPORTED_PARAMETERS.START_DATE.getValue()); } - final Date endDate = presentMeetingDate.minusDays(1).toDate(); - calendarHistory.updateEndDate(endDate); + if (null != newMeetingDate) { + final Date endDate = presentMeetingDate.minusDays(1).toDate(); + calendarHistory.updateEndDate(endDate); + } this.calendarHistoryRepository.save(calendarHistory); Set<CalendarHistory> history = calendarForUpdate.getCalendarHistory(); history.add(calendarHistory); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java index d32b218..7abb445 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/common/domain/NthDayType.java @@ -25,6 +25,8 @@ public enum NthDayType { THREE(3,"nthDayType.three"), FOUR(4,"nthDayType.four"), FIVE(5,"nthDayType.five"), + LAST(-1,"nthDayType.last"), + ONDAY(-2,"nthDayType.onday"), INVALID(0,"nthDayType.invalid"); private final Integer value; @@ -62,6 +64,12 @@ public enum NthDayType { case 5: repaymentFrequencyNthDayType = NthDayType.FIVE; break; + case -1: + repaymentFrequencyNthDayType = NthDayType.LAST; + break; + case -2: + repaymentFrequencyNthDayType = NthDayType.ONDAY; + break; default: break; } @@ -69,4 +77,13 @@ public enum NthDayType { return repaymentFrequencyNthDayType; } + public boolean isInvalid() { + return this.value.equals(NthDayType.INVALID.value); + } + public boolean isLastDay() { + return this.value.equals(NthDayType.LAST.value); + } + public boolean isOnDay() { + return this.value.equals(NthDayType.ONDAY.value); + } } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java index c44b8e4..fc503b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java @@ -52,6 +52,7 @@ import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.calendar.service.CalendarEnumerations; import org.apache.fineract.portfolio.calendar.service.CalendarReadPlatformService; +import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.domain.ClientEnumerations; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; @@ -271,10 +272,12 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService final LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); final String recurrence = rs.getString("recurrence"); final LocalTime meetingTime = JdbcSupport.getLocalTime(rs,"meetingTime"); + Integer monthOnDay = CalendarUtils.getMonthOnDay(recurrence); CalendarData calendarData = CalendarData.instance(calendarId, calendarInstanceId, entityId, entityType, title, description, location, startDate, endDate, null, null, false, recurrence, null, null, null, null, null, null, null, null, null, - null, null, null, null,meetingTime); + null, null, null, null, null, meetingTime, monthOnDay); + return CenterData.instance(id, accountNo, name, externalId, status, activationDate, officeId, null, staffId, staffName, hierarchy, null, calendarData); } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java index 61ea803..41d6c0b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanApiConstants.java @@ -40,12 +40,49 @@ public interface LoanApiConstants { public static final String rejectedOnDateParameterName = "rejectedOnDate"; public static final String withdrawnOnDateParameterName = "withdrawnOnDate"; - // Interest recalculation related - public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; + public static final String transactionProcessingStrategyIdParameterName = "transactionProcessingStrategyId"; + public static final String loanPurposeIdParameterName = "loanPurposeId"; + public static final String loanOfficerIdParameterName = "loanOfficerId"; + public static final String fundIdParameterName = "fundId"; + public static final String externalIdParameterName = "externalId"; + public static final String accountNoParameterName = "accountNo"; + public static final String productIdParameterName = "productId"; + public static final String calendarIdParameterName = "calendarId"; + public static final String loanTypeParameterName = "loanType"; + public static final String groupIdParameterName = "groupId"; + public static final String clientIdParameterName = "clientId"; + public static final String idParameterName = "id"; + public static final String graceOnInterestChargedParameterName = "graceOnInterestCharged"; + public static final String graceOnInterestPaymentParameterName = "graceOnInterestPayment"; + public static final String graceOnPrincipalPaymentParameterName = "graceOnPrincipalPayment"; + public static final String repaymentsStartingFromDateParameterName = "repaymentsStartingFromDate"; + public static final String interestRateFrequencyTypeParameterName = "interestRateFrequencyType"; + public static final String interestCalculationPeriodTypeParameterName = "interestCalculationPeriodType"; + public static final String interestTypeParameterName = "interestType"; + public static final String amortizationTypeParameterName = "amortizationType"; + public static final String repaymentFrequencyTypeParameterName = "repaymentFrequencyType"; + public static final String loanTermFrequencyTypeParameterName = "loanTermFrequencyType"; + public static final String loanTermFrequencyParameterName = "loanTermFrequency"; + public static final String numberOfRepaymentsParameterName = "numberOfRepayments"; + public static final String repaymentEveryParameterName = "repaymentEvery"; + public static final String interestRatePerPeriodParameterName = "interestRatePerPeriod"; + public static final String inArrearsToleranceParameterName = "inArrearsTolerance"; + public static final String interestChargedFromDateParameterName = "interestChargedFromDate"; + public static final String submittedOnDateParameterName = "submittedOnDate"; + public static final String submittedOnNoteParameterName = "interestChargedFromDate"; + public static final String collateralParameterName = "collateral"; + public static final String syncDisbursementWithMeetingParameterName = "syncDisbursementWithMeeting"; + public static final String linkAccountIdParameterName = "linkAccountId"; + public static final String createStandingInstructionAtDisbursementParameterName = "createStandingInstructionAtDisbursement"; public static final String daysInYearTypeParameterName = "daysInYearType"; public static final String daysInMonthTypeParameterName = "daysInMonthType"; + + // Interest recalculation related + public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod"; public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; + public static final String repaymentFrequencyNthDayTypeParameterName = "repaymentFrequencyNthDayType"; + public static final String repaymentFrequencyDayOfWeekTypeParameterName = "repaymentFrequencyDayOfWeekType"; // Floating interest rate related public static final String interestRateDifferentialParameterName = "interestRateDifferential"; http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 929a5ae..5e71e03 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -372,6 +372,17 @@ public class LoansApiResource { loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData, compoundingCalendarData); } + if (loanBasicDetails.isMonthlyRepaymentFrequencyType()) { + Collection<CalendarData> loanCalendarDatas = this.calendarReadPlatformService + .retrieveCalendarsByEntity(loanId, + CalendarEntityType.LOANS.getValue(), null); + CalendarData calendarData = null; + if (!CollectionUtils.isEmpty(loanCalendarDatas)) { + calendarData = loanCalendarDatas.iterator().next(); + } + if(calendarData != null) + loanBasicDetails = LoanAccountData.withLoanCalendarData(loanBasicDetails, calendarData); + } Collection<InterestRatePeriodData> interestRatesPeriods = this.loanReadPlatformService.retrieveLoanInterestRatePeriodData(loanId); @@ -484,6 +495,8 @@ public class LoansApiResource { LoanProductData product = null; Collection<EnumOptionData> loanTermFrequencyTypeOptions = null; Collection<EnumOptionData> repaymentFrequencyTypeOptions = null; + Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null; + Collection<EnumOptionData> repaymentFrequencyDayOfWeekTypeOptions = null; Collection<TransactionProcessingStrategyData> repaymentStrategyOptions = null; Collection<EnumOptionData> interestRateFrequencyTypeOptions = null; Collection<EnumOptionData> amortizationTypeOptions = null; @@ -506,6 +519,8 @@ public class LoansApiResource { loanBasicDetails.setProduct(product); loanTermFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); repaymentFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); + repaymentFrequencyNthDayTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyOptionsForNthDayOfMonth(); + repaymentFrequencyDayOfWeekTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyOptionsForDaysOfWeek(); interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); @@ -559,9 +574,10 @@ public class LoansApiResource { final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments, charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, - null, null, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, - interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, - loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, + repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, + interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, + fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions, + calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, overdueCharges, paidInAdvanceTemplate, interestRatesPeriods); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index cc7fc87..5975ec3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -34,6 +34,7 @@ import org.apache.fineract.portfolio.account.data.PortfolioAccountData; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.charge.data.ChargeData; import org.apache.fineract.portfolio.collateral.data.CollateralData; +import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType; import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData; import org.apache.fineract.portfolio.fund.data.FundData; import org.apache.fineract.portfolio.group.data.GroupGeneralData; @@ -1143,6 +1144,33 @@ public class LoanAccountData { acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap); } + public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { + return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, + acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, + acc.isLoanProductLinkedToFloatingRate, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, + acc.loanOfficerName, acc.currency, acc.proposedPrincipal, acc.principal, acc.approvedPrincipal, acc.totalOverpaid, + acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, acc.numberOfRepayments, acc.repaymentEvery, + acc.repaymentFrequencyType, calendarData.getRepeatsOnNthDayOfMonth(), calendarData.getRepeatsOnDay(), + acc.transactionProcessingStrategyId, acc.transactionProcessingStrategyName, acc.amortizationType, + acc.interestRatePerPeriod, acc.interestRateFrequencyType, acc.annualInterestRate, acc.interestType, + acc.isFloatingInterestRate, acc.interestRateDifferential, acc.interestCalculationPeriodType, + acc.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, + acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, acc.graceOnInterestCharged, + acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, + acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, + acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, + acc.repaymentFrequencyDaysOfWeekTypeOptions, acc.transactionProcessingStrategyOptions, + acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions, + acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, acc.loanOfficerOptions, + acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, + acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, + acc.multiDisburseLoan, acc.canDefineInstallmentAmount, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, + acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap); + } + public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientAccountNo, acc.clientName, @@ -1518,4 +1546,8 @@ public class LoanAccountData { return BigDecimal.ZERO; } + public boolean isMonthlyRepaymentFrequencyType() { + return (this.repaymentFrequencyType.getId().intValue() == PeriodFrequencyType.MONTHS.getValue()); + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java index 4b432e6..3cc4541 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanInterestRecalculationData.java @@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.data; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.portfolio.calendar.data.CalendarData; -import org.joda.time.LocalDate; public class LoanInterestRecalculationData { @@ -32,19 +31,29 @@ public class LoanInterestRecalculationData { private final CalendarData calendarData; private final EnumOptionData recalculationRestFrequencyType; private final Integer recalculationRestFrequencyInterval; - private final LocalDate recalculationRestFrequencyDate; + /* private final LocalDate recalculationRestFrequencyDate; */ + private final EnumOptionData recalculationRestFrequencyNthDay; + private final EnumOptionData recalculationRestFrequencyWeekday; + private final Integer recalculationRestFrequencyOnDay; private final EnumOptionData recalculationCompoundingFrequencyType; private final Integer recalculationCompoundingFrequencyInterval; - private final LocalDate recalculationCompoundingFrequencyDate; + /* private final LocalDate recalculationCompoundingFrequencyDate; */ + private final EnumOptionData recalculationCompoundingFrequencyNthDay; + private final EnumOptionData recalculationCompoundingFrequencyWeekday; + private final Integer recalculationCompoundingFrequencyOnDay; + private final Boolean isCompoundingToBePostedAsTransaction; @SuppressWarnings("unused") private final CalendarData compoundingCalendarData; + private final Boolean allowCompoundingOnEod; public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType, final CalendarData calendarData, final EnumOptionData recalculationRestFrequencyType, final Integer recalculationRestFrequencyInterval, - final LocalDate recalculationRestFrequencyDate, final CalendarData compoundingCalendarData, + final EnumOptionData recalculationRestFrequencyNthDay, final EnumOptionData recalculationRestFrequencyWeekday, + final Integer recalculationRestFrequencyOnDay, final CalendarData compoundingCalendarData, final EnumOptionData recalculationCompoundingFrequencyType, final Integer recalculationCompoundingFrequencyInterval, - final LocalDate recalculationCompoundingFrequencyDate) { + final EnumOptionData recalculationCompoundingFrequencyNthDay, final EnumOptionData recalculationCompoundingFrequencyWeekday, + final Integer recalculationCompoundingFrequencyOnDay, final Boolean isCompoundingToBePostedAsTransaction, final Boolean allowCompoundingOnEod) { this.id = id; this.loanId = loanId; this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; @@ -52,11 +61,17 @@ public class LoanInterestRecalculationData { this.calendarData = calendarData; this.recalculationRestFrequencyType = recalculationRestFrequencyType; this.recalculationRestFrequencyInterval = recalculationRestFrequencyInterval; - this.recalculationRestFrequencyDate = recalculationRestFrequencyDate; + this.recalculationRestFrequencyNthDay = recalculationRestFrequencyNthDay; + this.recalculationRestFrequencyWeekday = recalculationRestFrequencyWeekday; + this.recalculationRestFrequencyOnDay = recalculationRestFrequencyOnDay; this.recalculationCompoundingFrequencyType = recalculationCompoundingFrequencyType; this.recalculationCompoundingFrequencyInterval = recalculationCompoundingFrequencyInterval; - this.recalculationCompoundingFrequencyDate = recalculationCompoundingFrequencyDate; + this.recalculationCompoundingFrequencyNthDay = recalculationCompoundingFrequencyNthDay; + this.recalculationCompoundingFrequencyWeekday = recalculationCompoundingFrequencyWeekday; + this.recalculationCompoundingFrequencyOnDay = recalculationCompoundingFrequencyOnDay; this.compoundingCalendarData = compoundingCalendarData; + this.isCompoundingToBePostedAsTransaction = isCompoundingToBePostedAsTransaction; + this.allowCompoundingOnEod = allowCompoundingOnEod; } public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData, @@ -64,9 +79,12 @@ public class LoanInterestRecalculationData { return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId, recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData, recalculationData.recalculationRestFrequencyType, recalculationData.recalculationRestFrequencyInterval, - recalculationData.recalculationRestFrequencyDate, compoundingCalendarData, + recalculationData.recalculationRestFrequencyNthDay, recalculationData.recalculationRestFrequencyWeekday, + recalculationData.recalculationRestFrequencyOnDay, compoundingCalendarData, recalculationData.recalculationCompoundingFrequencyType, recalculationData.recalculationCompoundingFrequencyInterval, - recalculationData.recalculationCompoundingFrequencyDate); + recalculationData.recalculationCompoundingFrequencyNthDay, recalculationData.recalculationCompoundingFrequencyWeekday, + recalculationData.recalculationCompoundingFrequencyOnDay, recalculationData.isCompoundingToBePostedAsTransaction, + recalculationData.allowCompoundingOnEod); } public Long getId() { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/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 13fd9ce..822b575 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 @@ -24,6 +24,7 @@ import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -76,6 +77,7 @@ 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.domain.CalendarWeekDaysType; import org.apache.fineract.portfolio.calendar.service.CalendarUtils; import org.apache.fineract.portfolio.charge.domain.Charge; import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType; @@ -115,7 +117,6 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplica import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod; -import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; @@ -192,12 +193,6 @@ public class Loan extends AbstractPersistable<Long> { @Embedded private LoanProductRelatedDetail loanRepaymentScheduleDetail; - @Column(name = "repayment_frequency_nth_day_enum", nullable = true) - private Integer repaymentFrequencyNthDayType; - - @Column(name = "repayment_frequency_day_of_week_enum", nullable = true) - private Integer repaymentFrequencyDayOfWeekType; - @Column(name = "term_frequency", nullable = false) private Integer termFrequency; @@ -858,7 +853,7 @@ public class Loan extends AbstractPersistable<Long> { if (loanCharge.isOverdueInstallmentCharge()) { return loanCharge.getAmountPercentageAppliedTo(); } switch (loanCharge.getChargeCalculation()) { case PERCENT_OF_AMOUNT: - amount = getDerivedAmountForCharge(loanCharge); + amount = getDerivedAmountForCharge(loanCharge); break; case PERCENT_OF_AMOUNT_AND_INTEREST: final BigDecimal totalInterestCharged = getTotalInterest(); @@ -1135,7 +1130,8 @@ public class Loan extends AbstractPersistable<Long> { scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(), - scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent()); + scheduledLoanInstallment.penaltyChargesDue(), scheduledLoanInstallment.isRecalculatedInterestComponent(), + scheduledLoanInstallment.getLoanCompoundingDetails()); addRepaymentScheduleInstallment(installment); } } @@ -1194,6 +1190,13 @@ public class Loan extends AbstractPersistable<Long> { } installment.updateAccrualPortion(interest, fee, penality); } + LoanRepaymentScheduleInstallment lastInstallment = this.repaymentScheduleInstallments + .get(this.repaymentScheduleInstallments.size() - 1); + for (LoanTransaction loanTransaction : accruals) { + if (loanTransaction.getTransactionDate().isAfter(lastInstallment.getDueDate()) && !loanTransaction.isReversed()) { + loanTransaction.reverse(); + } + } } private void updateAccrualsForNonPeriodicAccruals(final Collection<LoanTransaction> accruals, final AppUser currentUser) { @@ -1265,12 +1268,6 @@ public class Loan extends AbstractPersistable<Long> { final String dateFormatAsInput = command.dateFormat(); final String localeAsInput = command.locale(); - final LocalDate recalculationRestFrequencyDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationRestFrequencyDateParamName); - final LocalDate recalculationCompoundingFrequencyDate = command - .localDateValueOfParameterNamed(LoanProductConstants.recalculationCompoundingFrequencyDateParamName); - updateLoanInterestRecalculationSettings(recalculationRestFrequencyDate, recalculationCompoundingFrequencyDate, command, - actualChanges); final String accountNoParamName = "accountNo"; if (command.isChangeInStringParameterNamed(accountNoParamName, this.accountNumber)) { @@ -1556,37 +1553,6 @@ public class Loan extends AbstractPersistable<Long> { /** * Update interest recalculation settings if product configuration changes */ - public void updateLoanInterestRecalculationSettings(final LocalDate recalculationRestFrequencyDate, - final LocalDate recalculationCompoundingFrequencyDate, final JsonCommand command, final Map<String, Object> actualChanges) { - - if (isInterestRecalculationEnabledForProduct()) { - Date restFrequencyDate = null; - if (recalculationRestFrequencyDate != null) { - restFrequencyDate = recalculationRestFrequencyDate.toDate(); - } - - Date compoundingFrequencyDate = null; - if (recalculationCompoundingFrequencyDate != null) { - compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate(); - } - if (this.loanInterestRecalculationDetails == null) { - actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, true); - this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct - .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct - .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct - .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); - this.loanInterestRecalculationDetails.updateLoan(this); - } else { - - this.loanInterestRecalculationDetails.update(command, actualChanges); - } - } else { - this.loanInterestRecalculationDetails = null; - } - } private void updateOverdueScheduleInstallment(final LoanCharge loanCharge) { if (loanCharge.isOverdueInstallmentCharge() && loanCharge.isActive()) { @@ -1911,8 +1877,7 @@ public class Loan extends AbstractPersistable<Long> { public void loanApplicationSubmittal(final AppUser currentUser, final LoanScheduleModel loanSchedule, final LoanApplicationTerms loanApplicationTerms, final LoanLifecycleStateMachine lifecycleStateMachine, final LocalDate submittedOn, final String externalId, final boolean allowTransactionsOnHoliday, final List<Holiday> holidays, - final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay, final LocalDate recalculationRestFrequencyDate, - final LocalDate recalculationCompoundingFrequencyDate) { + final WorkingDays workingDays, final boolean allowTransactionsOnNonWorkingDay) { updateLoanSchedule(loanSchedule, currentUser); @@ -1933,14 +1898,6 @@ public class Loan extends AbstractPersistable<Long> { this.expectedFirstRepaymentOnDate = loanApplicationTerms.getRepaymentStartFromDate(); this.interestChargedFromDate = loanApplicationTerms.getInterestChargedFromDate(); - if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.MONTHS) { - this.repaymentFrequencyNthDayType = loanApplicationTerms.getNthDay(); - this.repaymentFrequencyDayOfWeekType = loanApplicationTerms.getWeekDayType().getValue(); - } else { - this.repaymentFrequencyNthDayType = NthDayType.INVALID.getValue(); - this.repaymentFrequencyDayOfWeekType = DayOfWeekType.INVALID.getValue(); - } - updateLoanScheduleDependentDerivedFields(); if (submittedOn.isAfter(DateUtils.getLocalDateOfTenant())) { @@ -1984,22 +1941,9 @@ public class Loan extends AbstractPersistable<Long> { * enabled */ if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { - Date restFrequencyDate = null; - if (recalculationRestFrequencyDate != null) { - restFrequencyDate = recalculationRestFrequencyDate.toDate(); - } - Date compoundingFrequencyDate = null; - if (recalculationCompoundingFrequencyDate != null) { - compoundingFrequencyDate = recalculationCompoundingFrequencyDate.toDate(); - } this.loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom(this.loanProduct - .getProductInterestRecalculationDetails().getInterestRecalculationCompoundingMethod(), this.loanProduct - .getProductInterestRecalculationDetails().getRescheduleStrategyMethod(), this.loanProduct - .getProductInterestRecalculationDetails().getRestFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getRestInterval(), restFrequencyDate, this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingFrequencyType().getValue(), this.loanProduct - .getProductInterestRecalculationDetails().getCompoundingInterval(), compoundingFrequencyDate); + .getProductInterestRecalculationDetails()); this.loanInterestRecalculationDetails.updateLoan(this); } @@ -2299,7 +2243,7 @@ public class Loan extends AbstractPersistable<Long> { } public ChangedTransactionDetail disburse(final AppUser currentUser, final JsonCommand command, final Map<String, Object> actualChanges, - final ScheduleGeneratorDTO scheduleGeneratorDTO,final PaymentDetail paymentDetail) { + final ScheduleGeneratorDTO scheduleGeneratorDTO, final PaymentDetail paymentDetail) { final LoanStatus statusEnum = this.loanLifecycleStateMachine.transition(LoanEvent.LOAN_DISBURSED, LoanStatus.fromInt(this.loanStatus)); @@ -2330,7 +2274,7 @@ public class Loan extends AbstractPersistable<Long> { updateSummaryWithTotalFeeChargesDueAtDisbursement(deriveSumTotalOfChargesDueAtDisbursement()); updateLoanRepaymentPeriodsDerivedFields(actualDisbursementDate); LocalDateTime createdDate = DateUtils.getLocalDateTimeOfTenant(); - handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser,paymentDetail); + handleDisbursementTransaction(actualDisbursementDate, createdDate, currentUser, paymentDetail); updateLoanSummaryDerivedFields(); final Money interestApplied = Money.of(getCurrency(), this.summary.getTotalInterestCharged()); @@ -2380,8 +2324,13 @@ public class Loan extends AbstractPersistable<Long> { this.loanTermVariations.add(loanVariationTerms); } - if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged || rescheduledRepaymentDate != null) { - regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + if (isRepaymentScheduleRegenerationRequiredForDisbursement(actualDisbursementDate) || recalculateSchedule || isEmiAmountChanged + || rescheduledRepaymentDate != null) { + if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO, currentUser); + } else { + regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); + } } } @@ -2592,7 +2541,7 @@ public class Loan extends AbstractPersistable<Long> { updateLoanSchedule(loanSchedule, currentUser); Set<LoanCharge> charges = this.charges(); for (LoanCharge loanCharge : charges) { - recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); + recalculateLoanCharge(loanCharge, scheduleGeneratorDTO.getPenaltyWaitPeriod()); } } @@ -2633,7 +2582,8 @@ public class Loan extends AbstractPersistable<Long> { return interestRate; } - private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser, final PaymentDetail paymentDetail) { + private void handleDisbursementTransaction(final LocalDate disbursedOn, final LocalDateTime createdDate, final AppUser currentUser, + final PaymentDetail paymentDetail) { // add repayment transaction to track incoming money from client to mfi // for (charges due at time of disbursement) @@ -2651,14 +2601,14 @@ public class Loan extends AbstractPersistable<Long> { **/ Money disbursentMoney = Money.zero(getCurrency()); - final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail, disbursedOn, - null, createdDate, currentUser); + final LoanTransaction chargesPayment = LoanTransaction.repaymentAtDisbursement(getOffice(), disbursentMoney, paymentDetail, + disbursedOn, null, createdDate, currentUser); final Integer installmentNumber = null; for (final LoanCharge charge : charges()) { Date actualDisbursementDate = getActualDisbursementDate(charge); - if ((charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue() - && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null - && !charge.isWaived() && !charge.isFullyPaid()) + if ((charge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue() + && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null && !charge.isWaived() && !charge + .isFullyPaid()) || (charge.getCharge().getChargeTimeType() == ChargeTimeType.TRANCHE_DISBURSEMENT.getValue() && disbursedOn.equals(new LocalDate(actualDisbursementDate)) && actualDisbursementDate != null && !charge.isWaived() && !charge.isFullyPaid())) { @@ -3088,6 +3038,18 @@ public class Loan extends AbstractPersistable<Long> { return installment; } + private List<LoanTransaction> retreiveListOfIncomePostingTransactions() { + final List<LoanTransaction> incomePostTransactions = new ArrayList<>(); + for (final LoanTransaction transaction : this.loanTransactions) { + if (transaction.isNotReversed() && transaction.isIncomePosting()) { + incomePostTransactions.add(transaction); + } + } + final LoanTransactionComparator transactionComparator = new LoanTransactionComparator(); + Collections.sort(incomePostTransactions, transactionComparator); + return incomePostTransactions; + } + private List<LoanTransaction> retreiveListOfTransactionsPostDisbursement() { final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { @@ -3104,8 +3066,8 @@ public class Loan extends AbstractPersistable<Long> { final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>(); for (final LoanTransaction transaction : this.loanTransactions) { if (transaction.isNotReversed() - && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() || transaction - .isNonMonetaryTransaction())) { + && !(transaction.isDisbursement() || transaction.isAccrual() || transaction.isRepaymentAtDisbursement() + || transaction.isNonMonetaryTransaction() || transaction.isIncomePosting())) { repaymentsOrWaivers.add(transaction); } } @@ -3169,6 +3131,71 @@ public class Loan extends AbstractPersistable<Long> { LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); } + processIncomeAccrualTransactionOnLoanClosure(); + } + + private void processIncomeAccrualTransactionOnLoanClosure() { + if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() + && this.status().isClosedObligationsMet()) { + Date closedDate = this.getClosedOnDate(); + LocalDate closedLocalDate = new LocalDate(closedDate); + reverseTransactionsOnOrAfter(retreiveListOfIncomePostingTransactions(), closedDate); + reverseTransactionsOnOrAfter(retreiveListOfAccrualTransactions(), closedDate); + HashMap<String, BigDecimal> cumulativeIncomeFromInstallments = new HashMap<>(); + determineCumulativeIncomeFromInstallments(cumulativeIncomeFromInstallments); + HashMap<String, BigDecimal> cumulativeIncomeFromIncomePosting = new HashMap<>(); + determineCumulativeIncomeDetails(retreiveListOfIncomePostingTransactions(), cumulativeIncomeFromIncomePosting); + BigDecimal interestToPost = cumulativeIncomeFromInstallments.get("interest").subtract( + cumulativeIncomeFromIncomePosting.get("interest")); + BigDecimal feeToPost = cumulativeIncomeFromInstallments.get("fee").subtract(cumulativeIncomeFromIncomePosting.get("fee")); + BigDecimal penaltyToPost = cumulativeIncomeFromInstallments.get("penalty").subtract( + cumulativeIncomeFromIncomePosting.get("penalty")); + BigDecimal amountToPost = interestToPost.add(feeToPost).add(penaltyToPost); + LoanTransaction finalIncomeTransaction = LoanTransaction.incomePosting(this, this.getOffice(), closedDate, amountToPost, + interestToPost, feeToPost, penaltyToPost, null); + this.loanTransactions.add(finalIncomeTransaction); + if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + List<LoanTransaction> updatedAccrualTransactions = retreiveListOfAccrualTransactions(); + LocalDate lastAccruedDate = this.getDisbursementDate(); + if (updatedAccrualTransactions != null && updatedAccrualTransactions.size() > 0) { + lastAccruedDate = updatedAccrualTransactions.get(updatedAccrualTransactions.size() - 1).getTransactionDate(); + } + HashMap<String, Object> feeDetails = new HashMap<>(); + determineFeeDetails(lastAccruedDate, closedLocalDate, feeDetails); + LoanTransaction finalAccrual = LoanTransaction.accrueTransaction(this, this.getOffice(), closedLocalDate, amountToPost, + interestToPost, feeToPost, penaltyToPost, null); + updateLoanChargesPaidBy(finalAccrual, feeDetails, null); + this.loanTransactions.add(finalAccrual); + } + } + } + + private void determineCumulativeIncomeFromInstallments(HashMap<String, BigDecimal> cumulativeIncomeFromInstallments) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalty = BigDecimal.ZERO; + for (LoanRepaymentScheduleInstallment installment : this.repaymentScheduleInstallments) { + interest = interest.add(installment.getInterestCharged(getCurrency()).getAmount()); + fee = fee.add(installment.getFeeChargesCharged(getCurrency()).getAmount()); + penalty = penalty.add(installment.getPenaltyChargesCharged(getCurrency()).getAmount()); + } + cumulativeIncomeFromInstallments.put("interest", interest); + cumulativeIncomeFromInstallments.put("fee", fee); + cumulativeIncomeFromInstallments.put("penalty", penalty); + } + + private void determineCumulativeIncomeDetails(Collection<LoanTransaction> transactions, HashMap<String, BigDecimal> incomeDetailsMap) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalty = BigDecimal.ZERO; + for (LoanTransaction transaction : transactions) { + interest = interest.add(transaction.getInterestPortion(getCurrency()).getAmount()); + fee = fee.add(transaction.getFeeChargesPortion(getCurrency()).getAmount()); + penalty = penalty.add(transaction.getPenaltyChargesPortion(getCurrency()).getAmount()); + } + incomeDetailsMap.put("interest", interest); + incomeDetailsMap.put("fee", fee); + incomeDetailsMap.put("penalty", penalty); } private void handleLoanOverpayment(final LoanLifecycleStateMachine loanLifecycleStateMachine) { @@ -4566,7 +4593,7 @@ public class Loan extends AbstractPersistable<Long> { private LocalDate getLastUserTransactionDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { - if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) { + if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting())) { if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); } @@ -4982,6 +5009,179 @@ public class Loan extends AbstractPersistable<Long> { } processPostDisbursementTransactions(); + processIncomeTransactions(currentUser); + } + + private void updateLoanChargesPaidBy(LoanTransaction accrual, HashMap<String, Object> feeDetails, + LoanRepaymentScheduleInstallment installment) { + @SuppressWarnings("unchecked") + List<LoanCharge> loanCharges = (List<LoanCharge>) feeDetails.get("loanCharges"); + @SuppressWarnings("unchecked") + List<LoanInstallmentCharge> loanInstallmentCharges = (List<LoanInstallmentCharge>) feeDetails.get("loanInstallmentCharges"); + if (loanCharges != null) { + for (LoanCharge loanCharge : loanCharges) { + Integer installmentNumber = null == installment ? null : installment.getInstallmentNumber(); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanCharge, loanCharge.getAmount(getCurrency()) + .getAmount(), installmentNumber); + accrual.getLoanChargesPaid().add(loanChargePaidBy); + } + } + if (loanInstallmentCharges != null) { + for (LoanInstallmentCharge loanInstallmentCharge : loanInstallmentCharges) { + Integer installmentNumber = null == loanInstallmentCharge.getInstallment() ? null : loanInstallmentCharge.getInstallment() + .getInstallmentNumber(); + final LoanChargePaidBy loanChargePaidBy = new LoanChargePaidBy(accrual, loanInstallmentCharge.getLoancharge(), + loanInstallmentCharge.getAmount(getCurrency()).getAmount(), installmentNumber); + accrual.getLoanChargesPaid().add(loanChargePaidBy); + } + } + } + + public void processIncomeTransactions(AppUser currentUser) { + if (this.loanInterestRecalculationDetails != null && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()) { + LocalDate lastCompoundingDate = this.getDisbursementDate(); + List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = extractInterestRecalculationAdditionalDetails(); + List<LoanTransaction> incomeTransactions = retreiveListOfIncomePostingTransactions(); + List<LoanTransaction> accrualTransactions = retreiveListOfAccrualTransactions(); + for (LoanInterestRecalcualtionAdditionalDetails compoundingDetail : compoundingDetails) { + if (!compoundingDetail.getEffectiveDate().isBefore(DateUtils.getLocalDateOfTenant())) { + break; + } + LoanTransaction incomeTransaction = getTransactionForDate(incomeTransactions, compoundingDetail.getEffectiveDate()); + LoanTransaction accrualTransaction = getTransactionForDate(accrualTransactions, compoundingDetail.getEffectiveDate()); + addUpdateIncomeAndAccrualTransaction(compoundingDetail, lastCompoundingDate, currentUser, incomeTransaction, + accrualTransaction); + lastCompoundingDate = compoundingDetail.getEffectiveDate(); + } + } + } + + private void reverseTransactionsOnOrAfter(List<LoanTransaction> transactions, Date date) { + LocalDate refDate = new LocalDate(date); + for (LoanTransaction loanTransaction : transactions) { + if (!loanTransaction.getTransactionDate().isBefore(refDate)) { + loanTransaction.reverse(); + } + } + } + + private void addUpdateIncomeAndAccrualTransaction(LoanInterestRecalcualtionAdditionalDetails compoundingDetail, + LocalDate lastCompoundingDate, AppUser currentUser, LoanTransaction existingIncomeTransaction, + LoanTransaction existingAccrualTransaction) { + BigDecimal interest = BigDecimal.ZERO; + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalties = BigDecimal.ZERO; + HashMap<String, Object> feeDetails = new HashMap<>(); + + if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.INTEREST)) { + interest = compoundingDetail.getAmount(); + } else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.FEE)) { + determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails); + fee = (BigDecimal) feeDetails.get("fee"); + penalties = (BigDecimal) feeDetails.get("penalties"); + } else if (this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod().equals( + InterestRecalculationCompoundingMethod.INTEREST_AND_FEE)) { + determineFeeDetails(lastCompoundingDate, compoundingDetail.getEffectiveDate(), feeDetails); + fee = (BigDecimal) feeDetails.get("fee"); + penalties = (BigDecimal) feeDetails.get("penalties"); + interest = compoundingDetail.getAmount().subtract(fee).subtract(penalties); + } + + if (existingIncomeTransaction == null) { + LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate() + .toDate(), compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + this.loanTransactions.add(transaction); + } else if (existingIncomeTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { + existingIncomeTransaction.reverse(); + LoanTransaction transaction = LoanTransaction.incomePosting(this, this.getOffice(), compoundingDetail.getEffectiveDate() + .toDate(), compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + this.loanTransactions.add(transaction); + } + + if (isPeriodicAccrualAccountingEnabledOnLoanProduct()) { + if (existingAccrualTransaction == null) { + LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), + compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + updateLoanChargesPaidBy(accrual, feeDetails, null); + this.loanTransactions.add(accrual); + } else if (existingAccrualTransaction.getAmount(getCurrency()).getAmount().compareTo(compoundingDetail.getAmount()) != 0) { + existingAccrualTransaction.reverse(); + LoanTransaction accrual = LoanTransaction.accrueTransaction(this, this.getOffice(), compoundingDetail.getEffectiveDate(), + compoundingDetail.getAmount(), interest, fee, penalties, currentUser); + updateLoanChargesPaidBy(accrual, feeDetails, null); + this.loanTransactions.add(accrual); + } + } + updateLoanOutstandingBalaces(); + } + + private void determineFeeDetails(LocalDate fromDate, LocalDate toDate, HashMap<String, Object> feeDetails) { + BigDecimal fee = BigDecimal.ZERO; + BigDecimal penalties = BigDecimal.ZERO; + + List<Integer> installments = new ArrayList<>(); + for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { + if (loanRepaymentScheduleInstallment.getDueDate().isAfter(fromDate) + && !loanRepaymentScheduleInstallment.getDueDate().isAfter(toDate)) { + installments.add(loanRepaymentScheduleInstallment.getInstallmentNumber()); + } + } + + List<LoanCharge> loanCharges = new ArrayList<>(); + List<LoanInstallmentCharge> loanInstallmentCharges = new ArrayList<>(); + for (LoanCharge loanCharge : this.charges()) { + if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(fromDate, toDate)) { + if (loanCharge.isPenaltyCharge() && !loanCharge.isInstalmentFee()) { + penalties = penalties.add(loanCharge.amount()); + loanCharges.add(loanCharge); + } else if (!loanCharge.isInstalmentFee()) { + fee = fee.add(loanCharge.amount()); + loanCharges.add(loanCharge); + } + } else if (loanCharge.isInstalmentFee()) { + for (LoanInstallmentCharge installmentCharge : loanCharge.installmentCharges()) { + if (installments.contains(installmentCharge.getRepaymentInstallment().getInstallmentNumber())) { + fee = fee.add(installmentCharge.getAmount()); + loanInstallmentCharges.add(installmentCharge); + } + } + } + } + + feeDetails.put("fee", fee); + feeDetails.put("penalties", penalties); + feeDetails.put("loanCharges", loanCharges); + feeDetails.put("loanInstallmentCharges", loanInstallmentCharges); + } + + private LoanTransaction getTransactionForDate(List<LoanTransaction> transactions, LocalDate effectiveDate) { + for (LoanTransaction loanTransaction : transactions) { + if (loanTransaction.getTransactionDate().isEqual(effectiveDate)) { return loanTransaction; } + } + return null; + } + + private List<LoanInterestRecalcualtionAdditionalDetails> extractInterestRecalculationAdditionalDetails() { + List<LoanInterestRecalcualtionAdditionalDetails> retDetails = new ArrayList<>(); + if (null != this.repaymentScheduleInstallments && this.repaymentScheduleInstallments.size() > 0) { + Iterator<LoanRepaymentScheduleInstallment> installmentsItr = this.repaymentScheduleInstallments.iterator(); + while (installmentsItr.hasNext()) { + LoanRepaymentScheduleInstallment installment = installmentsItr.next(); + if (null != installment.getLoanCompoundingDetails()) { + retDetails.addAll(installment.getLoanCompoundingDetails()); + } + } + } + Collections.sort(retDetails, new Comparator<LoanInterestRecalcualtionAdditionalDetails>() { + + @Override + public int compare(LoanInterestRecalcualtionAdditionalDetails first, LoanInterestRecalcualtionAdditionalDetails second) { + return first.getEffectiveDate().compareTo(second.getEffectiveDate()); + } + }); + return retDetails; } public void processPostDisbursementTransactions() { @@ -5044,21 +5244,27 @@ public class Loan extends AbstractPersistable<Long> { public LoanApplicationTerms constructLoanApplicationTerms(final ScheduleGeneratorDTO scheduleGeneratorDTO) { final Integer loanTermFrequency = this.termFrequency; final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(this.termPeriodFrequencyType); - final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType); - final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType); + NthDayType nthDayType = null; + DayOfWeekType dayOfWeekType = null; final List<DisbursementData> disbursementData = new ArrayList<>(); for (LoanDisbursementDetails disbursementDetails : this.disbursementDetails) { disbursementData.add(disbursementDetails.toData()); } + Calendar calendar = scheduleGeneratorDTO.getCalendar(); + if (calendar != null) { + nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(calendar.getRecurrence()); + dayOfWeekType = DayOfWeekType.fromInt(CalendarUtils.getRepeatsOnDay(calendar.getRecurrence()).getValue()); + } + HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO(); CalendarInstance restCalendarInstance = null; CalendarInstance compoundingCalendarInstance = null; RecalculationFrequencyType recalculationFrequencyType = null; InterestRecalculationCompoundingMethod compoundingMethod = null; RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - Calendar calendar = null; CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + boolean allowCompoundingOnEod = false; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { restCalendarInstance = scheduleGeneratorDTO.getCalendarInstanceForInterestRecalculation(); compoundingCalendarInstance = scheduleGeneratorDTO.getCompoundingCalendarInstance(); @@ -5066,6 +5272,8 @@ public class Loan extends AbstractPersistable<Long> { compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); + allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); + calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); } calendar = scheduleGeneratorDTO.getCalendar(); calendarHistoryDataWrapper = scheduleGeneratorDTO.getCalendarHistoryDataWrapper(); @@ -5075,7 +5283,7 @@ public class Loan extends AbstractPersistable<Long> { List<LoanTermVariationsData> loanTermVariations = new ArrayList<>(); annualNominalInterestRate = constructLoanTermVariations(floatingRateDTO, annualNominalInterestRate, loanTermVariations); LocalDate interestChargedFromDate = getInterestChargedFromDate(); - if(interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()){ + if (interestChargedFromDate == null && scheduleGeneratorDTO.isInterestChargedFromDateAsDisbursementDateEnabled()) { interestChargedFromDate = getDisbursementDate(); } @@ -5087,7 +5295,7 @@ public class Loan extends AbstractPersistable<Long> { this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, calendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, calendarHistoryDataWrapper, - scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth()); + scheduleGeneratorDTO.getNumberOfdays(), scheduleGeneratorDTO.isSkipRepaymentOnFirstDayofMonth(), holidayDetailDTO, allowCompoundingOnEod); return loanApplicationTerms; } @@ -5105,6 +5313,7 @@ public class Loan extends AbstractPersistable<Long> { Money penaltyCharges = Money.zero(loanCurrency()); Money totalPrincipal = Money.zero(loanCurrency()); Money totalInterest = Money.zero(loanCurrency()); + final List<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null; for (final LoanRepaymentScheduleInstallment scheduledRepayment : this.repaymentScheduleInstallments) { totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency())); totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency())); @@ -5112,7 +5321,7 @@ public class Loan extends AbstractPersistable<Long> { penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency())); } return new LoanRepaymentScheduleInstallment(null, 0, LocalDate.now(), LocalDate.now(), totalPrincipal.getAmount(), - totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false); + totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false, compoundingDetails); } public List<LoanRepaymentScheduleInstallment> fetchRepaymentScheduleInstallments() { @@ -5141,11 +5350,17 @@ public class Loan extends AbstractPersistable<Long> { Money outstanding = Money.zero(getCurrency()); List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals(); for (LoanTransaction loanTransaction : loanTransactions) { - if (loanTransaction.isDisbursement()) { + if (loanTransaction.isDisbursement() || loanTransaction.isIncomePosting()) { outstanding = outstanding.plus(loanTransaction.getAmount(getCurrency())); loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount()); } else { - outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency())); + if (this.loanInterestRecalculationDetails != null + && this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction() + && !loanTransaction.isRepaymentAtDisbursement()) { + outstanding = outstanding.minus(loanTransaction.getAmount(getCurrency())); + } else { + outstanding = outstanding.minus(loanTransaction.getPrincipalPortion(getCurrency())); + } loanTransaction.updateOutstandingLoanBalance(outstanding.getAmount()); } } @@ -5252,7 +5467,8 @@ public class Loan extends AbstractPersistable<Long> { @SuppressWarnings({ "unused" }) public LoanApplicationTerms getLoanApplicationTerms(final ApplicationCurrency applicationCurrency, final CalendarInstance restCalendarInstance, CalendarInstance compoundingCalendarInstance, final Calendar loanCalendar, - final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays) { + final FloatingRateDTO floatingRateDTO, final boolean isSkipRepaymentonmonthFirst, final Integer numberofdays, + final HolidayDetailDTO holidayDetailDTO) { LoanProduct loanProduct = loanProduct(); // LoanProductRelatedDetail loanProductRelatedDetail = // getLoanRepaymentScheduleDetail(); @@ -5260,8 +5476,15 @@ public class Loan extends AbstractPersistable<Long> { final Integer loanTermFrequency = getTermFrequency(); final PeriodFrequencyType loanTermPeriodFrequencyType = this.loanRepaymentScheduleDetail.getInterestPeriodFrequencyType(); - final NthDayType nthDayType = NthDayType.fromInt(this.repaymentFrequencyNthDayType); - final DayOfWeekType dayOfWeekType = DayOfWeekType.fromInt(this.repaymentFrequencyDayOfWeekType); + NthDayType nthDayType = null; + DayOfWeekType dayOfWeekType = null; + if (loanCalendar != null) { + nthDayType = CalendarUtils.getRepeatsOnNthDayOfMonth(loanCalendar.getRecurrence()); + CalendarWeekDaysType getRepeatsOnDay = CalendarUtils.getRepeatsOnDay(loanCalendar.getRecurrence()); + Integer getRepeatsOnDayValue = null; + if (getRepeatsOnDay != null) getRepeatsOnDayValue = getRepeatsOnDay.getValue(); + if (getRepeatsOnDayValue != null) dayOfWeekType = DayOfWeekType.fromInt(getRepeatsOnDayValue); + } final Integer numberOfRepayments = this.loanRepaymentScheduleDetail.getNumberOfRepayments(); final Integer repaymentEvery = this.loanRepaymentScheduleDetail.getRepayEvery(); @@ -5304,7 +5527,7 @@ 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(); @@ -5315,11 +5538,13 @@ public class Loan extends AbstractPersistable<Long> { InterestRecalculationCompoundingMethod compoundingMethod = null; RecalculationFrequencyType compoundingFrequencyType = null; LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + boolean allowCompoundingOnEod = false; if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) { recalculationFrequencyType = this.loanInterestRecalculationDetails.getRestFrequencyType(); compoundingMethod = this.loanInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(); compoundingFrequencyType = this.loanInterestRecalculationDetails.getCompoundingFrequencyType(); rescheduleStrategyMethod = this.loanInterestRecalculationDetails.getRescheduleStrategyMethod(); + allowCompoundingOnEod = this.loanInterestRecalculationDetails.allowCompoundingOnEod(); } List<LoanTermVariationsData> loanTermVariations = new ArrayList<>(); @@ -5332,7 +5557,7 @@ public class Loan extends AbstractPersistable<Long> { this.loanProduct.getInstallmentAmountInMultiplesOf(), recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, this.loanProduct.preCloseInterestCalculationStrategy(), rescheduleStrategyMethod, loanCalendar, getApprovedPrincipal(), annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst); + calendarHistoryDataWrapper, numberofdays, isSkipRepaymentonmonthFirst, holidayDetailDTO, allowCompoundingOnEod); } /** @@ -5376,22 +5601,6 @@ public class Loan extends AbstractPersistable<Long> { return isEnabled; } - public Integer getRepaymentFrequencyNthDayType() { - return this.repaymentFrequencyNthDayType; - } - - public void setRepaymentFrequencyNthDayType(Integer repaymentFrequencyNthDayType) { - this.repaymentFrequencyNthDayType = repaymentFrequencyNthDayType; - } - - public Integer getRepaymentFrequencyDayOfWeekType() { - return this.repaymentFrequencyDayOfWeekType; - } - - public void setRepaymentFrequencyDayOfWeekType(Integer repaymentFrequencyDayOfWeekType) { - this.repaymentFrequencyDayOfWeekType = repaymentFrequencyDayOfWeekType; - } - public String getAccountNumber() { return this.accountNumber; } @@ -5597,8 +5806,10 @@ public class Loan extends AbstractPersistable<Long> { updateLoanToLastDisbursalState(actualDisbursementDate); for (Iterator<LoanTermVariations> iterator = this.loanTermVariations.iterator(); iterator.hasNext();) { LoanTermVariations loanTermVariations = iterator.next(); - if (loanTermVariations.getTermType().isDueDateVariation() && loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate) || - loanTermVariations.getTermType().isEMIAmountVariation() && loanTermVariations.getTermApplicableFrom().equals(actualDisbursementDate.toDate()) + if (loanTermVariations.getTermType().isDueDateVariation() + && loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate) + || loanTermVariations.getTermType().isEMIAmountVariation() + && loanTermVariations.getTermApplicableFrom().equals(actualDisbursementDate.toDate()) || loanTermVariations.getTermApplicableFrom().after(actualDisbursementDate.toDate())) { iterator.remove(); } @@ -5722,7 +5933,7 @@ public class Loan extends AbstractPersistable<Long> { } return nextRepaymentDate; } - + public BigDecimal getDerivedAmountForCharge(LoanCharge loanCharge) { BigDecimal amount = BigDecimal.ZERO; if (isMultiDisburmentLoan() && (loanCharge.getCharge().getChargeTimeType() == ChargeTimeType.DISBURSEMENT.getValue())) { http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java index 5a2241b..f1f8c4e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java @@ -288,4 +288,10 @@ public class LoanInstallmentCharge extends AbstractPersistable<Long> { return amountToDeductOnThisCharge; } + public LoanCharge getLoancharge() { + return this.loancharge; + } + public LoanRepaymentScheduleInstallment getInstallment() { + return this.installment; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/763cf18b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java new file mode 100644 index 0000000..f374760 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInterestRecalcualtionAdditionalDetails.java @@ -0,0 +1,62 @@ +/** + * 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.loanaccount.domain; + +import java.math.BigDecimal; +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.joda.time.LocalDate; +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_loan_interest_recalculation_additional_details") +public class LoanInterestRecalcualtionAdditionalDetails extends AbstractPersistable<Long> { + + @Temporal(TemporalType.DATE) + @Column(name = "effective_date") + private Date effectiveDate; + + @Column(name = "amount", scale = 6, precision = 19, nullable = false) + private BigDecimal amount; + + protected LoanInterestRecalcualtionAdditionalDetails() { + + } + + public LoanInterestRecalcualtionAdditionalDetails(final LocalDate effectiveDate, final BigDecimal amount) { + if (effectiveDate != null) { + this.effectiveDate = effectiveDate.toDate(); + } + this.amount = amount; + } + + public LocalDate getEffectiveDate() { + return new LocalDate(this.effectiveDate); + } + + public BigDecimal getAmount() { + return this.amount; + } +}
