From 437de26c33a800004af1918c48520713932e7e43 Mon Sep 17 00:00:00 2001 From: binnygopinath Date: Thu, 7 Apr 2016 10:44:02 +0530 Subject: [PATCH] FINERACT-94: Flexible morotorium(only principal portion) --- .../loanaccount/api/LoansApiResource.java | 2 +- .../loanaccount/data/LoanAccountData.java | 36 +- .../domain/LoanApplicationTerms.java | 3266 ++++++++++------- .../service/LoanScheduleAssembler.java | 3 +- .../RescheduleLoansApiConstants.java | 3 +- ...ateLoanScheduleQueryFromApiJsonHelper.java | 2 +- ...anApplicationCommandFromApiJsonHelper.java | 1 + .../service/LoanReadPlatformServiceImpl.java | 5 +- .../api/LoanProductsApiResource.java | 2 +- .../loanproduct/data/LoanProductData.java | 18 +- .../loanproduct/domain/LoanProduct.java | 7 +- ...MinimumRepaymentScheduleRelatedDetail.java | 2 + .../domain/LoanProductRelatedDetail.java | 40 +- .../LoanProductDataValidator.java | 2 +- .../LoanProductReadPlatformServiceImpl.java | 5 +- ...recurring_moratorium_principal_periods.sql | 5 + .../LoanProductRelatedDetailTestHelper.java | 3 +- 17 files changed, 1953 insertions(+), 1449 deletions(-) create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V298__recurring_moratorium_principal_periods.sql 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 86afeab8f..929a5aec6 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 @@ -129,7 +129,7 @@ public class LoansApiResource { "interestRatePerPeriod", "annualInterestRate", "repaymentFrequencyType", "transactionProcessingStrategyId", "transactionProcessingStrategyName", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "expectedFirstRepaymentOnDate", - "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "timeline", + "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "timeline", "totalFeeChargesAtDisbursement", "summary", "repaymentSchedule", "transactions", "charges", "collateral", "guarantors", "meeting", "productOptions", "amortizationTypeOptions", "interestTypeOptions", "interestCalculationPeriodTypeOptions", "repaymentFrequencyTypeOptions", "repaymentFrequencyNthDayTypeOptions", "repaymentFrequencyDaysOfWeekTypeOptions", 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 6ec674d47..cc7fc8729 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 @@ -111,6 +111,7 @@ public class LoanAccountData { private final Long transactionProcessingStrategyId; private final String transactionProcessingStrategyName; private final Integer graceOnPrincipalPayment; + private final Integer recurringMoratoriumOnPrincipalPeriods; private final Integer graceOnInterestPayment; private final Integer graceOnInterestCharged; private final Integer graceOnArrearsAgeing; @@ -254,6 +255,7 @@ public class LoanAccountData { final Boolean allowPartialPeriodInterestCalcualtion = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; @@ -321,7 +323,7 @@ public class LoanAccountData { repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, - allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, + allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, @@ -384,6 +386,7 @@ public class LoanAccountData { final Boolean allowPartialPeriodInterestCalcualtion = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnArrearsAgeing = null; final Integer graceOnInterestCharged = null; @@ -453,7 +456,7 @@ public class LoanAccountData { repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, - allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, + allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, @@ -479,7 +482,7 @@ public class LoanAccountData { acc.repaymentFrequencyDayOfWeekType, 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.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, clientAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, @@ -546,6 +549,7 @@ public class LoanAccountData { final Boolean allowPartialPeriodInterestCalcualtion = null; final LocalDate expectedFirstRepaymentOnDate = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; @@ -615,7 +619,7 @@ public class LoanAccountData { repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionProcessingStrategyId, transactionProcessingStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, - allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, + allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, @@ -641,7 +645,7 @@ public class LoanAccountData { 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.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, groupAcc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, @@ -790,7 +794,7 @@ public class LoanAccountData { product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(), product.getAllowPartialPeriodInterestCalcualtion(), - expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getGraceOnInterestPayment(), + expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getRecurringMoratoriumOnPrincipalPeriods(), product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), interestChargedFromDate, timeline, summary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, @@ -849,7 +853,7 @@ public class LoanAccountData { product.getAmortizationType(), product.getInterestRatePerPeriod(), product.getInterestRateFrequencyType(), product.getAnnualInterestRate(), product.getInterestType(), product.isFloatingInterestRateCalculationAllowed(), product.getDefaultDifferentialLendingRate(), product.getInterestCalculationPeriodType(), - product.getAllowPartialPeriodInterestCalcualtion(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), + product.getAllowPartialPeriodInterestCalcualtion(), acc.expectedFirstRepaymentOnDate, product.getGraceOnPrincipalPayment(), product.getRecurringMoratoriumOnPrincipalPeriods(), product.getGraceOnInterestPayment(), product.getGraceOnInterestCharged(), acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, null, null, @@ -884,7 +888,7 @@ public class LoanAccountData { final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, Boolean allowPartialPeriodInterestCalcualtion, final LocalDate expectedFirstRepaymentOnDate, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter, final Boolean multiDisburseLoan, Boolean canDefineInstallmentAmount, @@ -936,7 +940,7 @@ public class LoanAccountData { repaymentEvery, repaymentFrequencyType, repaymentFrequencyNthDayType, repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, - allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, + allowPartialPeriodInterestCalcualtion, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, @@ -985,7 +989,7 @@ public class LoanAccountData { 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.allowPartialPeriodInterestCalcualtion, acc.expectedFirstRepaymentOnDate, acc.graceOnPrincipalPayment, acc.recurringMoratoriumOnPrincipalPeriods, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, repaymentSchedule, transactions, charges, collateral, guarantors, calendarData, productOptions, termFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, @@ -1025,7 +1029,7 @@ public class LoanAccountData { 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.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, @@ -1089,7 +1093,7 @@ public class LoanAccountData { 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.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, @@ -1122,7 +1126,7 @@ public class LoanAccountData { 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.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, @@ -1150,7 +1154,7 @@ public class LoanAccountData { 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.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, @@ -1205,7 +1209,7 @@ public class LoanAccountData { final BigDecimal annualInterestRate, final EnumOptionData interestType, final boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final EnumOptionData interestCalculationPeriodType, final Boolean allowPartialPeriodInterestCalcualtion, final LocalDate expectedFirstRepaymentOnDate, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, final LoanApplicationTimelineData timeline, final LoanSummaryData summary, final BigDecimal feeChargesDueAtDisbursementCharged, final LoanScheduleData repaymentSchedule, final Collection transactions, final Collection charges, @@ -1279,6 +1283,8 @@ public class LoanAccountData { this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; this.expectedFirstRepaymentOnDate = expectedFirstRepaymentOnDate; this.graceOnPrincipalPayment = graceOnPrincipalPayment; + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; + this.graceOnInterestPayment = graceOnInterestPayment; this.graceOnInterestCharged = graceOnInterestCharged; this.interestChargedFromDate = interestChargedFromDate; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index d2341baad..7be11d321 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -57,1424 +57,1864 @@ import org.joda.time.Years; public final class LoanApplicationTerms { - private final ApplicationCurrency currency; - - private final Calendar loanCalendar; - private Integer loanTermFrequency; - private final PeriodFrequencyType loanTermPeriodFrequencyType; - private Integer numberOfRepayments; - private Integer actualNumberOfRepayments; - private final Integer repaymentEvery; - private final PeriodFrequencyType repaymentPeriodFrequencyType; - private final Integer nthDay; + private final ApplicationCurrency currency; + + private final Calendar loanCalendar; + private Integer loanTermFrequency; + private final PeriodFrequencyType loanTermPeriodFrequencyType; + private Integer numberOfRepayments; + private Integer actualNumberOfRepayments; + private final Integer repaymentEvery; + private final PeriodFrequencyType repaymentPeriodFrequencyType; + private final Integer nthDay; - private final DayOfWeekType weekDayType; - private final AmortizationMethod amortizationMethod; + private final DayOfWeekType weekDayType; + private final AmortizationMethod amortizationMethod; - private final InterestMethod interestMethod; - private BigDecimal interestRatePerPeriod; - private final PeriodFrequencyType interestRatePeriodFrequencyType; - private BigDecimal annualNominalInterestRate; - private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; - private final boolean allowPartialPeriodInterestCalcualtion; + private final InterestMethod interestMethod; + private BigDecimal interestRatePerPeriod; + private final PeriodFrequencyType interestRatePeriodFrequencyType; + private BigDecimal annualNominalInterestRate; + private final InterestCalculationPeriodMethod interestCalculationPeriodMethod; + private final boolean allowPartialPeriodInterestCalcualtion; - private Money principal; - private final LocalDate expectedDisbursementDate; - private final LocalDate repaymentsStartingFromDate; - private final LocalDate calculatedRepaymentsStartingFromDate; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the principal component of a - * loans repayment period (installment). - */ - private Integer principalGrace; + private Money principal; + private final LocalDate expectedDisbursementDate; + private final LocalDate repaymentsStartingFromDate; + private final LocalDate calculatedRepaymentsStartingFromDate; + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the principal component of a + * loans repayment period (installment). + */ + private Integer principalGrace; + private Integer recurringMoratoriumOnPrincipalPeriods; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the payment of interest in a - * loans repayment period (installment). - * - * Note: Interest is still calculated taking into account the full - * loan term, the interest is simply offset to a later period. - */ - private Integer interestPaymentGrace; + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the payment of interest in a + * loans repayment period (installment). + * + * Note: Interest is still calculated taking into account the full + * loan term, the interest is simply offset to a later period. + */ + private Integer interestPaymentGrace; - /** - * Integer representing the number of 'repayment frequencies' or - * installments where 'grace' should apply to the charging of interest in a - * loans repayment period (installment). - * - * Note: The loan is interest-free for the period of time - * indicated. - */ - private final Integer interestChargingGrace; + /** + * Integer representing the number of 'repayment frequencies' or + * installments where 'grace' should apply to the charging of interest in a + * loans repayment period (installment). + * + * Note: The loan is interest-free for the period of time + * indicated. + */ + private final Integer interestChargingGrace; - /** - * Legacy method of support 'grace' on the charging of interest on a loan. - * - *

- * For the typical structured loan, its reasonable to use an integer to - * indicate the number of 'repayment frequency' periods the 'grace' should - * apply to but for slightly irregular loans where the period between - * disbursement and the date of the 'first repayment period' isnt doest - * match the 'repayment frequency' but can be less (15days instead of 1 - * month) or more (6 weeks instead of 1 month) - The idea was to use a date - * to indicate from whence interest should be charged. - *

- */ - private LocalDate interestChargedFromDate; - private final Money inArrearsTolerance; - - private final Integer graceOnArrearsAgeing; - - // added - private LocalDate loanEndDate; - - private final List disbursementDatas; - - private final boolean multiDisburseLoan; - - private BigDecimal fixedEmiAmount; - - private BigDecimal fixedPrincipalAmount; - - private BigDecimal currentPeriodFixedEmiAmount; - - private BigDecimal currentPeriodFixedPrincipalAmount; - - private final BigDecimal actualFixedEmiAmount; - - private final BigDecimal maxOutstandingBalance; - - private Money totalInterestDue; - - private final DaysInMonthType daysInMonthType; - - private final DaysInYearType daysInYearType; - - private final boolean interestRecalculationEnabled; - - private final LoanRescheduleStrategyMethod rescheduleStrategyMethod; - - private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; - - private final CalendarInstance restCalendarInstance; - - private final RecalculationFrequencyType recalculationFrequencyType; - - private final CalendarInstance compoundingCalendarInstance; - - private final RecalculationFrequencyType compoundingFrequencyType; - - private final BigDecimal principalThresholdForLastInstalment; - private final Integer installmentAmountInMultiplesOf; - - private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; - - private Money approvedPrincipal = null; - - private final LoanTermVariationsDataWrapper variationsDataWrapper; - - private Money adjustPrincipalForFlatLoans; - - private final LocalDate seedDate; - - private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; - - private final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled; - - private final Integer numberOfDays; - - private final boolean isSkipRepaymentOnFirstDayOfMonth; - - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, - final PeriodFrequencyType repaymentPeriodFrequencyType, Integer nthDay, DayOfWeekType weekDayType, - final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, - final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate, - final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, - final Money principalMoney, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, final Integer graceOnPrincipalPayment, - final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final LocalDate interestChargedFromDate, - final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount, - final List disbursementDatas, final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, - final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, - final RecalculationFrequencyType recalculationFrequencyType, final CalendarInstance restCalendarInstance, - final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, - final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount, List loanTermVariations, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, - final Integer numberOfdays,boolean isSkipRepaymentOnFirstDayofMonth) { - - - final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, - repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, - interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, - interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, - graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, rescheduleStrategyMethod, - interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, - compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - preClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth); - - } - - 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 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, BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, - List loanTermVariations, final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth) { - final Calendar loanCalendar = null; - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - - return assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, nthDay, dayOfWeek, - expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, inArrearsTolerance, - loanProductRelatedDetail, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, interestChargedFromDate, - principalThresholdForLastInstalment, installmentAmountInMultiplesOf, recalculationFrequencyType, restCalendarInstance, - compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, loanPreClosureInterestCalculationStrategy, - rescheduleStrategyMethod, loanCalendar, approvedAmount, annualNominalInterestRate, loanTermVariations, - calendarHistoryDataWrapper, numberOfdays, isSkipRepaymentOnFirstDayofMonth); - } - - 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 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, BigDecimal approvedAmount, - BigDecimal annualNominalInterestRate, final List loanTermVariations, - final CalendarHistoryDataWrapper calendarHistoryDataWrapper, final Integer numberOfdays, - final boolean isSkipRepaymentOnFirstDayofMonth) { - - final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); - final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); - final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod(); - final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); - final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail - .getInterestCalculationPeriodMethod(); - final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion(); - final Money principalMoney = loanProductRelatedDetail.getPrincipal(); - - // - final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); - final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); - final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); - - // Interest recalculation settings - final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); - final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); - final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); - final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, nthDay.getValue(), dayOfWeek, amortizationMethod, interestMethod, - interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, - interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, - loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, compoundingMethod, restCalendarInstance, recalculationFrequencyType, compoundingCalendarInstance, - compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth); - } - - public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, 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 LocalDate interestChargedFromDate, final LoanInterestRecalculationDetails interestRecalculationDetails, - final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, - final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount, final BigDecimal annualNominalInterestRate, final List loanTermVariations, - Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) { - - final Integer numberOfRepayments = loanProductRelatedDetail.getNumberOfRepayments(); - final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); - final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail.getRepaymentPeriodFrequencyType(); - final AmortizationMethod amortizationMethod = loanProductRelatedDetail.getAmortizationMethod(); - final InterestMethod interestMethod = loanProductRelatedDetail.getInterestMethod(); - final BigDecimal interestRatePerPeriod = loanProductRelatedDetail.getNominalInterestRatePerPeriod(); - final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType(); - final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail - .getInterestCalculationPeriodMethod(); - final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion(); - final Money principalMoney = loanProductRelatedDetail.getPrincipal(); - - // - final Integer graceOnPrincipalPayment = loanProductRelatedDetail.graceOnPrincipalPayment(); - final Integer graceOnInterestPayment = loanProductRelatedDetail.graceOnInterestPayment(); - final Integer graceOnInterestCharged = loanProductRelatedDetail.graceOnInterestCharged(); - - // Interest recalculation settings - final DaysInMonthType daysInMonthType = loanProductRelatedDetail.fetchDaysInMonthType(); - final DaysInYearType daysInYearType = loanProductRelatedDetail.fetchDaysInYearType(); - final boolean isInterestRecalculationEnabled = loanProductRelatedDetail.isInterestRecalculationEnabled(); - LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; - InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; - if (isInterestRecalculationEnabled) { - rescheduleStrategyMethod = interestRecalculationDetails.getRescheduleStrategyMethod(); - interestRecalculationCompoundingMethod = interestRecalculationDetails.getInterestRecalculationCompoundingMethod(); - } - final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; - final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; - - return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, - repaymentEvery, repaymentPeriodFrequencyType, null, null, amortizationMethod, interestMethod, interestRatePerPeriod, - interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, - allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, - interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, - loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, daysInYearType, isInterestRecalculationEnabled, - rescheduleStrategyMethod, interestRecalculationCompoundingMethod, restCalendarInstance, recalculationFrequencyType, - compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, - loanPreClosureInterestCalculationStrategy, loanCalendar, approvedAmount, loanTermVariations, calendarHistoryDataWrapper, - isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfdays, isSkipRepaymentOnFirstDayofMonth); - - } - - public static LoanApplicationTerms assembleFrom(final LoanApplicationTerms applicationTerms, - final List loanTermVariations) { - return new LoanApplicationTerms(applicationTerms.currency, applicationTerms.loanTermFrequency, - applicationTerms.loanTermPeriodFrequencyType, applicationTerms.numberOfRepayments, applicationTerms.repaymentEvery, - applicationTerms.repaymentPeriodFrequencyType, applicationTerms.nthDay, applicationTerms.weekDayType, - applicationTerms.amortizationMethod, applicationTerms.interestMethod, applicationTerms.interestRatePerPeriod, - applicationTerms.interestRatePeriodFrequencyType, applicationTerms.annualNominalInterestRate, - applicationTerms.interestCalculationPeriodMethod, applicationTerms.allowPartialPeriodInterestCalcualtion, - applicationTerms.principal, applicationTerms.expectedDisbursementDate, applicationTerms.repaymentsStartingFromDate, - applicationTerms.calculatedRepaymentsStartingFromDate, applicationTerms.principalGrace, - applicationTerms.interestPaymentGrace, applicationTerms.interestChargingGrace, applicationTerms.interestChargedFromDate, - applicationTerms.inArrearsTolerance, applicationTerms.multiDisburseLoan, applicationTerms.actualFixedEmiAmount, - applicationTerms.disbursementDatas, applicationTerms.maxOutstandingBalance, applicationTerms.graceOnArrearsAgeing, - applicationTerms.daysInMonthType, applicationTerms.daysInYearType, applicationTerms.interestRecalculationEnabled, - applicationTerms.rescheduleStrategyMethod, applicationTerms.interestRecalculationCompoundingMethod, - applicationTerms.restCalendarInstance, applicationTerms.recalculationFrequencyType, - applicationTerms.compoundingCalendarInstance, applicationTerms.compoundingFrequencyType, - applicationTerms.principalThresholdForLastInstalment, applicationTerms.installmentAmountInMultiplesOf, - applicationTerms.preClosureInterestCalculationStrategy, applicationTerms.loanCalendar, - applicationTerms.approvedPrincipal.getAmount(), loanTermVariations, applicationTerms.calendarHistoryDataWrapper, - applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, applicationTerms.numberOfDays, - applicationTerms.isSkipRepaymentOnFirstDayOfMonth); - } - - private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, - final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, - final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer nthDay, final DayOfWeekType weekDayType, - final AmortizationMethod amortizationMethod, final InterestMethod interestMethod, final BigDecimal interestRatePerPeriod, - final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal annualNominalInterestRate, - final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, - final Money principal, final LocalDate expectedDisbursementDate, final LocalDate repaymentsStartingFromDate, - final LocalDate calculatedRepaymentsStartingFromDate, final Integer principalGrace, final Integer interestPaymentGrace, - final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance, - final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, - final BigDecimal maxOutstandingBalance, final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, - final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, - final LoanRescheduleStrategyMethod rescheduleStrategyMethod, - final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, - final CalendarInstance restCalendarInstance, final RecalculationFrequencyType recalculationFrequencyType, - final CalendarInstance compoundingCalendarInstance, final RecalculationFrequencyType compoundingFrequencyType, - final BigDecimal principalThresholdForLastInstalment, final Integer installmentAmountInMultiplesOf, - final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, final Calendar loanCalendar, - BigDecimal approvedAmount, List loanTermVariations, - final CalendarHistoryDataWrapper calendarHistoryDataWrapper, Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, - final Integer numberOfdays, final boolean isSkipRepaymentOnFirstDayofMonth) { - - this.currency = currency; - this.loanTermFrequency = loanTermFrequency; - this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; - this.numberOfRepayments = numberOfRepayments; - this.repaymentEvery = repaymentEvery; - this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; - this.nthDay = nthDay; - this.weekDayType = weekDayType; - this.amortizationMethod = amortizationMethod; - - this.interestMethod = interestMethod; - this.interestRatePerPeriod = interestRatePerPeriod; - this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; - this.annualNominalInterestRate = annualNominalInterestRate; - this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; - this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; - - this.principal = principal; - this.expectedDisbursementDate = expectedDisbursementDate; - this.repaymentsStartingFromDate = repaymentsStartingFromDate; - this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; - - this.principalGrace = principalGrace; - this.interestPaymentGrace = interestPaymentGrace; - this.interestChargingGrace = interestChargingGrace; - this.interestChargedFromDate = interestChargedFromDate; - - this.inArrearsTolerance = inArrearsTolerance; - this.multiDisburseLoan = multiDisburseLoan; - this.fixedEmiAmount = emiAmount; - this.actualFixedEmiAmount = emiAmount; - this.disbursementDatas = disbursementDatas; - this.maxOutstandingBalance = maxOutstandingBalance; - this.graceOnArrearsAgeing = graceOnArrearsAgeing; - this.daysInMonthType = daysInMonthType; - this.daysInYearType = daysInYearType; - this.interestRecalculationEnabled = isInterestRecalculationEnabled; - this.rescheduleStrategyMethod = rescheduleStrategyMethod; - this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; - this.restCalendarInstance = restCalendarInstance; - this.compoundingCalendarInstance = compoundingCalendarInstance; - this.recalculationFrequencyType = recalculationFrequencyType; - this.compoundingFrequencyType = compoundingFrequencyType; - this.principalThresholdForLastInstalment = principalThresholdForLastInstalment; - this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; - this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy; - this.isSkipRepaymentOnFirstDayOfMonth = isSkipRepaymentOnFirstDayofMonth; - this.numberOfDays = numberOfdays; - - this.loanCalendar = loanCalendar; - this.approvedPrincipal = Money.of(principal.getCurrency(), approvedAmount); - this.variationsDataWrapper = new LoanTermVariationsDataWrapper(loanTermVariations); - this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); - this.adjustPrincipalForFlatLoans = principal.zero(); - if (this.calculatedRepaymentsStartingFromDate == null) { - this.seedDate = this.expectedDisbursementDate; - } else { - this.seedDate = this.calculatedRepaymentsStartingFromDate; - } - this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; - this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; - } - - public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, - final int periodNumber) { - - Money adjusted = principalForPeriod; - - final Money totalPrincipalRemaining = this.principal.minus(totalCumulativePrincipalToDate); - if (totalPrincipalRemaining.isLessThanZero()) { - // paid too much principal, subtract amount that overpays from - // principal paid for period. - adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs()); - } else if (this.actualFixedEmiAmount != null) { - final Money difference = this.principal.minus(totalCumulativePrincipalToDate); - final Money principalThreshold = principalForPeriod.multipliedBy(this.principalThresholdForLastInstalment).dividedBy(100, - MoneyHelper.getRoundingMode()); - if (difference.isLessThan(principalThreshold)) { - adjusted = principalForPeriod.plus(difference.abs()); - } - } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) { - - final Money difference = totalCumulativePrincipalToDate.minus(this.principal); - if (difference.isLessThanZero()) { - adjusted = principalForPeriod.plus(difference.abs()); - } else if (difference.isGreaterThanZero()) { - adjusted = principalForPeriod.minus(difference.abs()); - } - } - - return adjusted; - } - - public Money adjustInterestIfLastRepaymentPeriod(final Money interestForThisPeriod, final Money totalCumulativeInterestToDate, - final Money totalInterestDueForLoan, final int periodNumber) { - - Money adjusted = interestForThisPeriod; - - final Money totalInterestRemaining = totalInterestDueForLoan.minus(totalCumulativeInterestToDate); - if (totalInterestRemaining.isLessThanZero()) { - // paid too much interest, subtract amount that overpays from - // interest paid for period. - adjusted = interestForThisPeriod.minus(totalInterestRemaining.abs()); - } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, periodNumber)) { - final Money interestDifference = totalCumulativeInterestToDate.minus(totalInterestDueForLoan); - if (interestDifference.isLessThanZero()) { - adjusted = interestForThisPeriod.plus(interestDifference.abs()); - } else if (interestDifference.isGreaterThanZero()) { - adjusted = interestForThisPeriod.minus(interestDifference.abs()); - } - } - if (adjusted.isLessThanZero()) { - adjusted = adjusted.plus(adjusted); - } - return adjusted; - } - - /** - * Calculates the total interest to be charged on loan taking into account - * grace settings. - * - */ - public Money calculateTotalInterestCharged(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { - - Money totalInterestCharged = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); - - final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); - - final Money totalGraceOnInterestCharged = totalInterestPerInstallment.multiplyRetainScale(getInterestChargingGrace(), - mc.getRoundingMode()); - - totalInterestCharged = totalInterestChargedForLoanTerm.minus(totalGraceOnInterestCharged); - break; - case DECLINING_BALANCE: - case INVALID: - break; - } - - return totalInterestCharged; - } - - public Money calculateTotalPrincipalForPeriod(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance, - final int periodNumber, final MathContext mc, Money interestForThisInstallment) { - - Money principalForInstallment = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace(mc, periodNumber); - break; - case DECLINING_BALANCE: - switch (this.amortizationMethod) { - case EQUAL_INSTALLMENTS: - Money totalPmtForThisInstallment = pmtForInstallment(calculator, outstandingBalance, periodNumber, mc); - principalForInstallment = calculatePrincipalDueForInstallment(periodNumber, totalPmtForThisInstallment, - interestForThisInstallment); - break; - case EQUAL_PRINCIPAL: - principalForInstallment = calculateEqualPrincipalDueForInstallment(mc, periodNumber); - break; - case INVALID: - break; - } - break; - case INVALID: - break; - } - - return principalForInstallment; - } - - public Money pmtForInstallment(final PaymentPeriodsInOneYearCalculator calculator, final Money outstandingBalance, - final int periodNumber, final MathContext mc) { - // Calculate exact period from disbursement date - final LocalDate periodStartDate = getExpectedDisbursementDate().withDayOfMonth(1); - final LocalDate periodEndDate = getPeriodEndDate(periodStartDate); - // equal installments - final int periodsElapsed = periodNumber - 1; - // with periodic interest for default month and year for - // equal installment - final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate(calculator, mc, DaysInMonthType.DAYS_30, - DaysInYearType.DAYS_365, periodStartDate, periodEndDate); - Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod(periodicInterestRateForRepaymentPeriod, - outstandingBalance, periodsElapsed); - return totalPmtForThisInstallment; - } - - private LocalDate getPeriodEndDate(final LocalDate startDate) { - LocalDate dueRepaymentPeriodDate = startDate; - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery); - break; - case WEEKS: - dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery); - break; - case MONTHS: - dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery); - break; - case YEARS: - dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery); - break; - case INVALID: - break; - } - return dueRepaymentPeriodDate; - } - - public PrincipalInterest calculateTotalInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final int periodNumber, final MathContext mc, - final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LocalDate periodStartDate, - final LocalDate periodEndDate) { - - Money interestForInstallment = this.principal.zero(); - Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy(); - - switch (this.interestMethod) { - case FLAT: - - switch (this.amortizationMethod) { - case EQUAL_INSTALLMENTS: - // average out outstanding interest over remaining - // instalments where interest is applicable - interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(calculator, periodNumber, - mc); - break; - case EQUAL_PRINCIPAL: - // interest follows time-value of money and is brought - // forward to next applicable interest payment period - final PrincipalInterest result = calculateTotalFlatInterestForPeriod(calculator, periodNumber, mc, - interestBroughtForwardDueToGrace); - interestForInstallment = result.interest(); - interestBroughtForwardDueToGrace = result.interestPaymentDueToGrace(); - break; - case INVALID: - break; - } - break; - case DECLINING_BALANCE: - - final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( - calculator, mc, outstandingBalance, periodStartDate, periodEndDate); - - final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace( - calculator, interestCalculationGraceOnRepaymentPeriodFraction, mc, outstandingBalance, periodNumber, - periodStartDate, periodEndDate); - - interestForInstallment = interestForThisInstallmentAfterGrace; - if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) { - interestForInstallment = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentAfterGrace); - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - interestForInstallment = interestForThisInstallmentAfterGrace; - } else { - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForThisInstallmentBeforeGrace); - } - break; - case INVALID: - break; - } - - return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace); - } - - private final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) { - return periodNumber == numberOfRepayments; - } - - /** - * general method to calculate totalInterestDue discounting any grace - * settings - */ - private Money calculateTotalFlatInterestDueWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { - - Money totalInterestDue = this.principal.zero(); - - switch (this.interestMethod) { - case FLAT: - final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm(calculator, mc); - totalInterestDue = this.principal.multiplyRetainScale(interestRateForLoanTerm, mc.getRoundingMode()); - - break; - case DECLINING_BALANCE: - break; - case INVALID: - break; - } - - if (this.totalInterestDue != null) { - totalInterestDue = this.totalInterestDue; - } - - return totalInterestDue; - } - - private BigDecimal calculateFlatInterestRateForLoanTerm(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - - final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); - - final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); - - return this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) - .multiply(loanTermFrequencyBigDecimal); - } - - private BigDecimal calculatePeriodsInLoanTerm() { - - BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency); - switch (this.interestCalculationPeriodMethod) { - case DAILY: - // number of days from 'ideal disbursement' to final date - - LocalDate loanStartDate = getExpectedDisbursementDate(); - if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) { - loanStartDate = getInterestChargedFromLocalDate(); - } - - final int periodsInLoanTermInteger = Days.daysBetween(loanStartDate, this.loanEndDate).getDays(); - periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); - break; - case INVALID: - break; - case SAME_AS_REPAYMENT_PERIOD: - if (this.allowPartialPeriodInterestCalcualtion) { - LocalDate startDate = getExpectedDisbursementDate(); - if (getInterestChargedFromDate() != null) { - startDate = getInterestChargedFromLocalDate(); - } - periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate); - } - break; - } - - return periodsInLoanTerm; - } - - public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final LocalDate endDate) { - BigDecimal numberOfPeriods = BigDecimal.ZERO; - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - int numberOfDays = Days.daysBetween(startDate, endDate).getDays(); - numberOfPeriods = BigDecimal.valueOf((double) numberOfDays); - break; - case WEEKS: - int numberOfWeeks = Weeks.weeksBetween(startDate, endDate).getWeeks(); - int daysLeftAfterWeeks = Days.daysBetween(startDate.plusWeeks(numberOfWeeks), endDate).getDays(); - numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)).add( - BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); - break; - case MONTHS: - int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths(); - LocalDate startDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths); - LocalDate endDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths + 1); - int daysLeftAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDate).getDays(); - int daysInPeriodAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDateAfterConsideringMonths).getDays(); - numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)).add( - BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths)); - break; - case YEARS: - int numberOfYears = Years.yearsBetween(startDate, endDate).getYears(); - LocalDate startDateAfterConsideringYears = startDate.plusYears(numberOfYears); - LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1); - int daysLeftAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDate).getDays(); - int daysInPeriodAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDateAfterConsideringYears).getDays(); - numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)).add( - BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears)); - break; - default: - break; - } - return numberOfPeriods; - } - - public void updateLoanEndDate(final LocalDate loanEndDate) { - this.loanEndDate = loanEndDate; - } - - private Money calculateTotalInterestPerInstallmentWithoutGrace(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc) { - - final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); - - return totalInterestForLoanTerm.dividedBy(Long.valueOf(this.actualNumberOfRepayments), mc.getRoundingMode()); - } - - private Money calculateTotalPrincipalPerPeriodWithoutGrace(final MathContext mc, final int periodNumber) { - final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); - Money principalPerPeriod = this.principal.dividedBy(totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus( - this.adjustPrincipalForFlatLoans); - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principalPerPeriod = principalPerPeriod.zero(); - } - if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) && currentPeriodFixedPrincipalAmount != null) { - this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans.plus(principalPerPeriod.minus( - currentPeriodFixedPrincipalAmount).dividedBy(this.actualNumberOfRepayments - periodNumber, mc.getRoundingMode())); - principalPerPeriod = this.principal.zero().plus(currentPeriodFixedPrincipalAmount); - - } - return principalPerPeriod; - } - - private PrincipalInterest calculateTotalFlatInterestForPeriod(final PaymentPeriodsInOneYearCalculator calculator, - final int periodNumber, final MathContext mc, final Money cumulatingInterestPaymentDueToGrace) { - - Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace.copy(); - - Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.plus(interestForInstallment); - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) { - interestForInstallment = cumulatingInterestPaymentDueToGrace.plus(interestForInstallment); - interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace.zero(); - } - - return new PrincipalInterest(null, interestForInstallment, interestBroughtForwardDueToGrace); - } - - /* - * calculates the interest that should be due for a given scheduled loan - * repayment period. It takes into account GRACE periods and calculates how - * much interest is due per period by averaging the number of periods where - * interest is due and should be paid against the total known interest that - * is due without grace. - */ - private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods(final PaymentPeriodsInOneYearCalculator calculator, - final int periodNumber, final MathContext mc) { - - Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else if (isInterestFreeGracePeriod(periodNumber)) { - interestForInstallment = interestForInstallment.zero(); - } else { - - final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace(calculator, mc); - - final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace(calculator, mc); - - final Money totalInterestFree = interestPerGracePeriod.multipliedBy(getInterestChargingGrace()); - final Money realTotalInterestForLoan = totalInterestForLoanTerm.minus(totalInterestFree); - - final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments); - - interestForInstallment = realTotalInterestForLoan - .dividedBy(BigDecimal.valueOf(interestPaymentDuePeriods), mc.getRoundingMode()); - } - - return interestForInstallment; - } - - private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, - final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, LocalDate periodStartDate, LocalDate periodEndDate) { - - final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); - - BigDecimal periodicInterestRate = BigDecimal.ZERO; - BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency(periodStartDate, periodEndDate); - switch (this.interestCalculationPeriodMethod) { - case INVALID: - break; - case DAILY: - // For daily work out number of days in the period - BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days.daysBetween(periodStartDate, periodEndDate).getDays()); - - final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc) - .divide(divisor, mc); - - switch (this.repaymentPeriodFrequencyType) { - case INVALID: - break; - case DAYS: - periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); - break; - case WEEKS: - periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); - break; - case MONTHS: - if (daysInMonthType.isDaysInMonth_30()) { - numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(30), mc); - } - periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); - break; - case YEARS: - switch (daysInYearType) { - case DAYS_360: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(360), mc); - break; - case DAYS_364: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(364), mc); - break; - case DAYS_365: - numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(365), mc); - break; - default: - break; - } - periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); - break; - } - break; - case SAME_AS_REPAYMENT_PERIOD: - periodicInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc) - .multiply(loanTermFrequencyBigDecimal); - break; - } - - return periodicInterestRate; - } - - private BigDecimal calculateLoanTermFrequency(final LocalDate periodStartDate, final LocalDate periodEndDate) { - BigDecimal loanTermFrequencyBigDecimal = BigDecimal.valueOf(this.repaymentEvery); - if (this.interestCalculationPeriodMethod.isDaily() || this.allowPartialPeriodInterestCalcualtion) { - loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates(periodStartDate, periodEndDate); - } - return loanTermFrequencyBigDecimal; - } - - public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, - final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { - - long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); - int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); - if (isFallingInRepaymentPeriod(fromDate, toDate)) { - loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); - repaymentEvery = getPeriodsBetween(fromDate, toDate); - } - - final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); - final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); - final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc).divide( - divisor, mc); - BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc); - return outstandingBalance.getAmount().multiply(interestRate, mc); - } - - private long calculatePeriodsInOneYear(final PaymentPeriodsInOneYearCalculator calculator) { - - // check if daysInYears is set if so change periodsInOneYear to days set - // in db - long periodsInOneYear; - boolean daysInYearToUse = (this.repaymentPeriodFrequencyType.getCode().equalsIgnoreCase("periodFrequencyType.days") && !this.daysInYearType - .getCode().equalsIgnoreCase("DaysInYearType.actual")); - if (daysInYearToUse) { - periodsInOneYear = this.daysInYearType.getValue().longValue(); - } else { - periodsInOneYear = calculator.calculate(this.repaymentPeriodFrequencyType).longValue(); - } - switch (this.interestCalculationPeriodMethod) { - case DAILY: - periodsInOneYear = (!this.daysInYearType.getCode().equalsIgnoreCase("DaysInYearType.actual")) ? this.daysInYearType - .getValue().longValue() : calculator.calculate(PeriodFrequencyType.DAYS).longValue(); - break; - case INVALID: - break; - case SAME_AS_REPAYMENT_PERIOD: - break; - } - - return periodsInOneYear; - } - - private int calculateNumberOfRepaymentsWithPrincipalPayment() { - return this.actualNumberOfRepayments - getPrincipalGrace(); - } - - private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(final Integer totalNumberOfRepaymentPeriods) { - return totalNumberOfRepaymentPeriods - Math.max(getInterestChargingGrace(), getInterestPaymentGrace()); - } - - private Integer calculateNumberOfPrincipalPaymentPeriods(final Integer totalNumberOfRepaymentPeriods) { - return totalNumberOfRepaymentPeriods - getPrincipalGrace(); - } - - public boolean isPrincipalGraceApplicableForThisPeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber <= getPrincipalGrace(); - } - - private boolean isInterestPaymentGraceApplicableForThisPeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); - } - - private boolean isFirstPeriodAfterInterestPaymentGracePeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber == getInterestPaymentGrace() + 1; - } - - private boolean isInterestFreeGracePeriod(final int periodNumber) { - return periodNumber > 0 && periodNumber <= getInterestChargingGrace(); - } - - public Integer getPrincipalGrace() { - Integer graceOnPrincipalPayments = Integer.valueOf(0); - if (this.principalGrace != null) { - graceOnPrincipalPayments = this.principalGrace; - } - return graceOnPrincipalPayments; - } - - public Integer getInterestPaymentGrace() { - Integer graceOnInterestPayments = Integer.valueOf(0); - if (this.interestPaymentGrace != null) { - graceOnInterestPayments = this.interestPaymentGrace; - } - return graceOnInterestPayments; - } - - public Integer getInterestChargingGrace() { - Integer graceOnInterestCharged = Integer.valueOf(0); - if (this.interestChargingGrace != null) { - graceOnInterestCharged = this.interestChargingGrace; - } - return graceOnInterestCharged; - } - - private double paymentPerPeriod(final BigDecimal periodicInterestRate, final Money balance, final int periodsElapsed) { - - if (getFixedEmiAmount() == null) { - final double futureValue = 0; - final double principalDouble = balance.getAmount().multiply(BigDecimal.valueOf(-1)).doubleValue(); - - final Integer periodsRemaining = this.actualNumberOfRepayments - periodsElapsed; - - double installmentAmount = FinanicalFunctions.pmt(periodicInterestRate.doubleValue(), periodsRemaining.doubleValue(), - principalDouble, futureValue, false); - - if (this.installmentAmountInMultiplesOf != null) { - installmentAmount = Money.roundToMultiplesOf(installmentAmount, this.installmentAmountInMultiplesOf); - } - setFixedEmiAmount(BigDecimal.valueOf(installmentAmount)); - } - return getFixedEmiAmount().doubleValue(); - } - - private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator, - final MathContext mc, final Money outstandingBalance, LocalDate periodStartDate, LocalDate periodEndDate) { - - Money interestDue = Money.zero(outstandingBalance.getCurrency()); - - final BigDecimal periodicInterestRate = periodicInterestRate(calculator, mc, this.daysInMonthType, this.daysInYearType, - periodStartDate, periodEndDate); - interestDue = outstandingBalance.multiplyRetainScale(periodicInterestRate, mc.getRoundingMode()); - - return interestDue; - } - - private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace(final PaymentPeriodsInOneYearCalculator calculator, - final double interestCalculationGraceOnRepaymentPeriodFraction, final MathContext mc, final Money outstandingBalance, - final int periodNumber, LocalDate periodStartDate, LocalDate periodEndDate) { - - Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace(calculator, mc, outstandingBalance, - periodStartDate, periodEndDate); - - if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { - interest = interest.zero(); - } - - Double fraction = interestCalculationGraceOnRepaymentPeriodFraction; - - if (isInterestFreeGracePeriod(periodNumber)) { - interest = interest.zero(); - } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { - - if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer.valueOf(1).doubleValue()) { - interest = interest.zero(); - fraction = fraction - Integer.valueOf(1).doubleValue(); - - } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.25") - && interestCalculationGraceOnRepaymentPeriodFraction < Integer.valueOf(1).doubleValue()) { - - final Money graceOnInterestForRepaymentPeriod = interest.multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); - interest = interest.minus(graceOnInterestForRepaymentPeriod); - fraction = Double.valueOf("0"); - } - } - - return interest; - } - - private boolean isInterestFreeGracePeriodFromDate(final double interestCalculationGraceOnRepaymentPeriodFraction) { - return this.interestChargedFromDate != null && interestCalculationGraceOnRepaymentPeriodFraction > Double.valueOf("0.0"); - } - - private Money calculateEqualPrincipalDueForInstallment(final MathContext mc, final int periodNumber) { - Money principal = this.principal; - if (this.fixedPrincipalAmount == null) { - final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments); - principal = this.principal.dividedBy(numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); - this.fixedPrincipalAmount = principal.getAmount(); - } - principal = Money.of(getCurrency(), getFixedPrincipalAmount()); - - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principal = principal.zero(); - } - return principal; - } - - public void updateFixedPrincipalAmount(final MathContext mc, final int periodNumber, final Money outstandingAmount) { - final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfPrincipalPaymentPeriods(this.actualNumberOfRepayments); - Money principal = outstandingAmount.dividedBy(numberOfPrincipalPaymentPeriods - periodNumber + 1, mc.getRoundingMode()); - this.fixedPrincipalAmount = principal.getAmount(); - } - - public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) { - this.fixedPrincipalAmount = fixedPrincipalAmount; - } - - private Money calculatePrincipalDueForInstallment(final int periodNumber, final Money totalDuePerInstallment, final Money periodInterest) { - - Money principal = totalDuePerInstallment.minus(periodInterest); - if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { - principal = principal.zero(); - } - return principal; - } - - private Money calculateTotalDueForEqualInstallmentRepaymentPeriod(final BigDecimal periodicInterestRate, final Money balance, - final int periodsElapsed) { - - final double paymentPerRepaymentPeriod = paymentPerPeriod(periodicInterestRate, balance, periodsElapsed); - - return Money.of(balance.getCurrency(), BigDecimal.valueOf(paymentPerRepaymentPeriod)); - } - - public LoanProductRelatedDetail toLoanProductRelatedDetail() { - final MonetaryCurrency currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDecimalPlaces(), - this.currency.getCurrencyInMultiplesOf()); - - return LoanProductRelatedDetail.createFrom(currency, this.principal.getAmount(), this.interestRatePerPeriod, - this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, - this.interestCalculationPeriodMethod, this.allowPartialPeriodInterestCalcualtion, this.repaymentEvery, - this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.interestPaymentGrace, - this.interestChargingGrace, this.amortizationMethod, this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, - this.daysInMonthType.getValue(), this.daysInYearType.getValue(), this.interestRecalculationEnabled); - } - - public Integer getLoanTermFrequency() { - return this.loanTermFrequency; - } - - public PeriodFrequencyType getLoanTermPeriodFrequencyType() { - return this.loanTermPeriodFrequencyType; - } - - public Integer getRepaymentEvery() { - return this.repaymentEvery; - } - - public PeriodFrequencyType getRepaymentPeriodFrequencyType() { - return this.repaymentPeriodFrequencyType; - } - - public Date getRepaymentStartFromDate() { - Date dateValue = null; - if (this.repaymentsStartingFromDate != null) { - dateValue = this.repaymentsStartingFromDate.toDate(); - } - return dateValue; - } - - public Date getInterestChargedFromDate() { - Date dateValue = null; - if (this.interestChargedFromDate != null) { - dateValue = this.interestChargedFromDate.toDate(); - } - return dateValue; - } - - public void setPrincipal(Money principal) { - this.principal = principal; - } - - public LocalDate getInterestChargedFromLocalDate() { - return this.interestChargedFromDate; - } - - public InterestMethod getInterestMethod() { - return this.interestMethod; - } - - public AmortizationMethod getAmortizationMethod() { - return this.amortizationMethod; - } - - public MonetaryCurrency getCurrency() { - return this.principal.getCurrency(); - } - - public Integer getNumberOfRepayments() { - return this.numberOfRepayments; - } - - public LocalDate getExpectedDisbursementDate() { - return this.expectedDisbursementDate; - } - - public LocalDate getRepaymentsStartingFromLocalDate() { - return this.repaymentsStartingFromDate; - } - - public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { - return this.calculatedRepaymentsStartingFromDate; - } - - public Money getPrincipal() { - return this.principal; - } - - public Money getApprovedPrincipal() { - return this.approvedPrincipal; - } - - public List getDisbursementDatas() { - return this.disbursementDatas; - } - - public boolean isMultiDisburseLoan() { - return this.multiDisburseLoan; - } - - public Money getMaxOutstandingBalance() { - return Money.of(getCurrency(), this.maxOutstandingBalance); - } - - public BigDecimal getFixedEmiAmount() { - BigDecimal fixedEmiAmount = this.fixedEmiAmount; - if (getCurrentPeriodFixedEmiAmount() != null) { - fixedEmiAmount = getCurrentPeriodFixedEmiAmount(); - } - return fixedEmiAmount; - } - - public Integer getNthDay() { - return this.nthDay; - } - - public DayOfWeekType getWeekDayType() { - return this.weekDayType; - } - - public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { - this.fixedEmiAmount = fixedEmiAmount; - } - - public void resetFixedEmiAmount() { - this.fixedEmiAmount = this.actualFixedEmiAmount; - } - - public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() { - return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT; - } - - public boolean isInterestRecalculationEnabled() { - return this.interestRecalculationEnabled; - } - - public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { - return this.rescheduleStrategyMethod; - } - - public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { - return this.interestRecalculationCompoundingMethod; - } - - public CalendarInstance getRestCalendarInstance() { - return this.restCalendarInstance; - } - - private boolean isFallingInRepaymentPeriod(LocalDate fromDate, LocalDate toDate) { - boolean isSameAsRepaymentPeriod = false; - if (this.interestCalculationPeriodMethod.getValue().equals(InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD.getValue())) { - switch (this.repaymentPeriodFrequencyType) { - case WEEKS: - int days = Days.daysBetween(fromDate, toDate).getDays(); - isSameAsRepaymentPeriod = (days % 7) == 0; - break; - case MONTHS: - boolean isFromDateOnEndDate = false; - if (fromDate.getDayOfMonth() > fromDate.plusDays(1).getDayOfMonth()) { - isFromDateOnEndDate = true; - } - boolean isToDateOnEndDate = false; - if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) { - isToDateOnEndDate = true; - } - - if (isFromDateOnEndDate && isToDateOnEndDate) { - isSameAsRepaymentPeriod = true; - } else { - - int months = getPeriodsBetween(fromDate, toDate); - fromDate = fromDate.plusMonths(months); - isSameAsRepaymentPeriod = fromDate.isEqual(toDate); - } - - break; - default: - break; - } - } - return isSameAsRepaymentPeriod; - } - - private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { - Integer numberOfPeriods = 0; - PeriodType periodType = PeriodType.yearMonthDay(); - Period difference = new Period(fromDate, toDate, periodType); - switch (this.repaymentPeriodFrequencyType) { - case DAYS: - numberOfPeriods = difference.getDays(); - break; - case WEEKS: - periodType = PeriodType.weeks(); - difference = new Period(fromDate, toDate, periodType); - numberOfPeriods = difference.getWeeks(); - break; - case MONTHS: - numberOfPeriods = difference.getMonths(); - break; - case YEARS: - numberOfPeriods = difference.getYears(); - break; - default: - break; - } - return numberOfPeriods; - } - - public RecalculationFrequencyType getRecalculationFrequencyType() { - return this.recalculationFrequencyType; - } - - public void updateNumberOfRepayments(final Integer numberOfRepayments) { - this.numberOfRepayments = numberOfRepayments; - this.actualNumberOfRepayments = numberOfRepayments + getLoanTermVariations().adjustNumberOfRepayments(); - - } - - public void updatePrincipalGrace(final Integer principalGrace) { - this.principalGrace = principalGrace; - } - - public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { - this.interestPaymentGrace = interestPaymentGrace; - } - - public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { - if (interestRatePerPeriod != null) { - this.interestRatePerPeriod = interestRatePerPeriod; - } - } - - public void updateAnnualNominalInterestRate(BigDecimal annualNominalInterestRate) { - if (annualNominalInterestRate != null) { - this.annualNominalInterestRate = annualNominalInterestRate; - } - } - - public BigDecimal getAnnualNominalInterestRate() { - return this.annualNominalInterestRate; - } - - public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { - if (interestChargedFromDate != null) { - this.interestChargedFromDate = interestChargedFromDate; - } - } - - public void updateLoanTermFrequency(Integer loanTermFrequency) { - if (loanTermFrequency != null) { - this.loanTermFrequency = loanTermFrequency; - } - } - - public void updateTotalInterestDue(Money totalInterestDue) { - - if (totalInterestDue != null) { - this.totalInterestDue = totalInterestDue; - } - } - - public ApplicationCurrency getApplicationCurrency() { - return this.currency; - } - - public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { - return this.interestCalculationPeriodMethod; - } - - public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { - return this.preClosureInterestCalculationStrategy; - } - - public CalendarInstance getCompoundingCalendarInstance() { - return this.compoundingCalendarInstance; - } - - public RecalculationFrequencyType getCompoundingFrequencyType() { - return this.compoundingFrequencyType; - } - - public BigDecimal getActualFixedEmiAmount() { - return this.actualFixedEmiAmount; - } - - public Calendar getLoanCalendar() { - return loanCalendar; - } - - public BigDecimal getFixedPrincipalAmount() { - BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount; - if (getCurrentPeriodFixedPrincipalAmount() != null) { - fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount(); - } - return fixedPrincipalAmount; - } - - public LoanTermVariationsDataWrapper getLoanTermVariations() { - return this.variationsDataWrapper; - } - - public BigDecimal getCurrentPeriodFixedEmiAmount() { - return this.currentPeriodFixedEmiAmount; - } - - public void setCurrentPeriodFixedEmiAmount(BigDecimal currentPeriodFixedEmiAmount) { - this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount; - } - - public BigDecimal getCurrentPeriodFixedPrincipalAmount() { - return this.currentPeriodFixedPrincipalAmount; - } - - public void setCurrentPeriodFixedPrincipalAmount(BigDecimal currentPeriodFixedPrincipalAmount) { - this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount; - } - - public Integer fetchNumberOfRepaymentsAfterExceptions() { - return this.actualNumberOfRepayments; - } - - public LocalDate getSeedDate() { - return this.seedDate; - } - - public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() { - return this.calendarHistoryDataWrapper; - } - - public Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled(){ - return this.isInterestChargedFromDateSameAsDisbursalDateEnabled; - } - - public Integer getNumberOfdays() { - return numberOfDays; - } - - public boolean isSkipRepaymentOnFirstDayofMonth() { - return isSkipRepaymentOnFirstDayOfMonth; - } + /** + * Legacy method of support 'grace' on the charging of interest on a loan. + * + *

+ * For the typical structured loan, its reasonable to use an integer to + * indicate the number of 'repayment frequency' periods the 'grace' should + * apply to but for slightly irregular loans where the period between + * disbursement and the date of the 'first repayment period' isnt doest + * match the 'repayment frequency' but can be less (15days instead of 1 + * month) or more (6 weeks instead of 1 month) - The idea was to use a date + * to indicate from whence interest should be charged. + *

+ */ + private LocalDate interestChargedFromDate; + private final Money inArrearsTolerance; + + private final Integer graceOnArrearsAgeing; + + // added + private LocalDate loanEndDate; + + private final List disbursementDatas; + + private final boolean multiDisburseLoan; + + private BigDecimal fixedEmiAmount; + + private BigDecimal fixedPrincipalAmount; + + private BigDecimal currentPeriodFixedEmiAmount; + + private BigDecimal currentPeriodFixedPrincipalAmount; + + private final BigDecimal actualFixedEmiAmount; + + private final BigDecimal maxOutstandingBalance; + + private Money totalInterestDue; + + private final DaysInMonthType daysInMonthType; + + private final DaysInYearType daysInYearType; + + private final boolean interestRecalculationEnabled; + + private final LoanRescheduleStrategyMethod rescheduleStrategyMethod; + + private final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod; + + private final CalendarInstance restCalendarInstance; + + private final RecalculationFrequencyType recalculationFrequencyType; + + private final CalendarInstance compoundingCalendarInstance; + + private final RecalculationFrequencyType compoundingFrequencyType; + + private final BigDecimal principalThresholdForLastInstalment; + private final Integer installmentAmountInMultiplesOf; + + private final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy; + + private Money approvedPrincipal = null; + + private final LoanTermVariationsDataWrapper variationsDataWrapper; + + private Money adjustPrincipalForFlatLoans; + + private final LocalDate seedDate; + + private final CalendarHistoryDataWrapper calendarHistoryDataWrapper; + + private final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled; + + private final Integer numberOfDays; + + private final boolean isSkipRepaymentOnFirstDayOfMonth; + + public static LoanApplicationTerms assembleFrom( + final ApplicationCurrency currency, + final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, + final Integer numberOfRepayments, + final Integer repaymentEvery, + final PeriodFrequencyType repaymentPeriodFrequencyType, + Integer nthDay, + DayOfWeekType weekDayType, + final AmortizationMethod amortizationMethod, + final InterestMethod interestMethod, + final BigDecimal interestRatePerPeriod, + final PeriodFrequencyType interestRatePeriodFrequencyType, + final BigDecimal annualNominalInterestRate, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, + final boolean allowPartialPeriodInterestCalcualtion, + final Money principalMoney, + final LocalDate expectedDisbursementDate, + final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, + final Integer graceOnPrincipalPayment, + final Integer recurringMoratoriumOnPrincipalPeriods, + final Integer graceOnInterestPayment, + final Integer graceOnInterestCharged, + final LocalDate interestChargedFromDate, + final Money inArrearsTolerance, + final boolean multiDisburseLoan, + final BigDecimal emiAmount, + final List disbursementDatas, + final BigDecimal maxOutstandingBalance, + final Integer graceOnArrearsAgeing, + final DaysInMonthType daysInMonthType, + final DaysInYearType daysInYearType, + final boolean isInterestRecalculationEnabled, + final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance restCalendarInstance, + final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, + final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, + final Calendar loanCalendar, BigDecimal approvedAmount, + List loanTermVariations, + Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, + final Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) { + + final LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + return new LoanApplicationTerms(currency, loanTermFrequency, + loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, nthDay, + weekDayType, amortizationMethod, interestMethod, + interestRatePerPeriod, interestRatePeriodFrequencyType, + annualNominalInterestRate, interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, + expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, + recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, + inArrearsTolerance, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, + daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, rescheduleStrategyMethod, + interestRecalculationCompoundingMethod, restCalendarInstance, + recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, + preClosureInterestCalculationStrategy, loanCalendar, + approvedAmount, loanTermVariations, calendarHistoryDataWrapper, + isInterestChargedFromDateSameAsDisbursalDateEnabled, + numberOfdays, isSkipRepaymentOnFirstDayofMonth); + + } + + 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 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, + BigDecimal approvedAmount, BigDecimal annualNominalInterestRate, + List loanTermVariations, + final Integer numberOfdays, + final boolean isSkipRepaymentOnFirstDayofMonth) { + final Calendar loanCalendar = null; + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + + return assembleFrom(applicationCurrency, loanTermFrequency, + loanTermPeriodFrequencyType, nthDay, dayOfWeek, + expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, inArrearsTolerance, + loanProductRelatedDetail, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, + interestChargedFromDate, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, recalculationFrequencyType, + restCalendarInstance, compoundingMethod, + compoundingCalendarInstance, compoundingFrequencyType, + loanPreClosureInterestCalculationStrategy, + rescheduleStrategyMethod, loanCalendar, approvedAmount, + annualNominalInterestRate, loanTermVariations, + calendarHistoryDataWrapper, numberOfdays, + isSkipRepaymentOnFirstDayofMonth); + } + + 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 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, BigDecimal approvedAmount, + BigDecimal annualNominalInterestRate, + final List loanTermVariations, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper, + final Integer numberOfdays, + final boolean isSkipRepaymentOnFirstDayofMonth) { + + final Integer numberOfRepayments = loanProductRelatedDetail + .getNumberOfRepayments(); + final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); + final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail + .getRepaymentPeriodFrequencyType(); + final AmortizationMethod amortizationMethod = loanProductRelatedDetail + .getAmortizationMethod(); + final InterestMethod interestMethod = loanProductRelatedDetail + .getInterestMethod(); + final BigDecimal interestRatePerPeriod = loanProductRelatedDetail + .getNominalInterestRatePerPeriod(); + final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail + .getInterestPeriodFrequencyType(); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail + .getInterestCalculationPeriodMethod(); + final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail + .isAllowPartialPeriodInterestCalcualtion(); + final Money principalMoney = loanProductRelatedDetail.getPrincipal(); + + // + final Integer graceOnPrincipalPayment = loanProductRelatedDetail + .graceOnPrincipalPayment(); + final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail + .recurringMoratoriumOnPrincipalPeriods(); + final Integer graceOnInterestPayment = loanProductRelatedDetail + .graceOnInterestPayment(); + final Integer graceOnInterestCharged = loanProductRelatedDetail + .graceOnInterestCharged(); + + // Interest recalculation settings + final DaysInMonthType daysInMonthType = loanProductRelatedDetail + .fetchDaysInMonthType(); + final DaysInYearType daysInYearType = loanProductRelatedDetail + .fetchDaysInYearType(); + final boolean isInterestRecalculationEnabled = loanProductRelatedDetail + .isInterestRecalculationEnabled(); + final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, + loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, + nthDay.getValue(), dayOfWeek, amortizationMethod, + interestMethod, interestRatePerPeriod, + interestRatePeriodFrequencyType, annualNominalInterestRate, + interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, + expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, + recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, + inArrearsTolerance, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, + loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, + rescheduleStrategyMethod, compoundingMethod, + restCalendarInstance, recalculationFrequencyType, + compoundingCalendarInstance, compoundingFrequencyType, + principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, + loanPreClosureInterestCalculationStrategy, loanCalendar, + approvedAmount, loanTermVariations, calendarHistoryDataWrapper, + isInterestChargedFromDateSameAsDisbursalDateEnabled, + numberOfdays, isSkipRepaymentOnFirstDayofMonth); + } + + public static LoanApplicationTerms assembleFrom( + final ApplicationCurrency applicationCurrency, + final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, + 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 LocalDate interestChargedFromDate, + final LoanInterestRecalculationDetails interestRecalculationDetails, + final CalendarInstance restCalendarInstance, + final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, + final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, + final LoanPreClosureInterestCalculationStrategy loanPreClosureInterestCalculationStrategy, + final Calendar loanCalendar, BigDecimal approvedAmount, + final BigDecimal annualNominalInterestRate, + final List loanTermVariations, + Integer numberOfdays, boolean isSkipRepaymentOnFirstDayofMonth) { + + final Integer numberOfRepayments = loanProductRelatedDetail + .getNumberOfRepayments(); + final Integer repaymentEvery = loanProductRelatedDetail.getRepayEvery(); + final PeriodFrequencyType repaymentPeriodFrequencyType = loanProductRelatedDetail + .getRepaymentPeriodFrequencyType(); + final AmortizationMethod amortizationMethod = loanProductRelatedDetail + .getAmortizationMethod(); + final InterestMethod interestMethod = loanProductRelatedDetail + .getInterestMethod(); + final BigDecimal interestRatePerPeriod = loanProductRelatedDetail + .getNominalInterestRatePerPeriod(); + final PeriodFrequencyType interestRatePeriodFrequencyType = loanProductRelatedDetail + .getInterestPeriodFrequencyType(); + final InterestCalculationPeriodMethod interestCalculationPeriodMethod = loanProductRelatedDetail + .getInterestCalculationPeriodMethod(); + final boolean allowPartialPeriodInterestCalcualtion = loanProductRelatedDetail + .isAllowPartialPeriodInterestCalcualtion(); + final Money principalMoney = loanProductRelatedDetail.getPrincipal(); + + // + final Integer graceOnPrincipalPayment = loanProductRelatedDetail + .graceOnPrincipalPayment(); + final Integer recurringMoratoriumOnPrincipalPeriods = loanProductRelatedDetail + .recurringMoratoriumOnPrincipalPeriods(); + final Integer graceOnInterestPayment = loanProductRelatedDetail + .graceOnInterestPayment(); + final Integer graceOnInterestCharged = loanProductRelatedDetail + .graceOnInterestCharged(); + + // Interest recalculation settings + final DaysInMonthType daysInMonthType = loanProductRelatedDetail + .fetchDaysInMonthType(); + final DaysInYearType daysInYearType = loanProductRelatedDetail + .fetchDaysInYearType(); + final boolean isInterestRecalculationEnabled = loanProductRelatedDetail + .isInterestRecalculationEnabled(); + LoanRescheduleStrategyMethod rescheduleStrategyMethod = null; + InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod = null; + if (isInterestRecalculationEnabled) { + rescheduleStrategyMethod = interestRecalculationDetails + .getRescheduleStrategyMethod(); + interestRecalculationCompoundingMethod = interestRecalculationDetails + .getInterestRecalculationCompoundingMethod(); + } + final CalendarHistoryDataWrapper calendarHistoryDataWrapper = null; + final boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = false; + + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, + loanTermPeriodFrequencyType, numberOfRepayments, + repaymentEvery, repaymentPeriodFrequencyType, null, null, + amortizationMethod, interestMethod, interestRatePerPeriod, + interestRatePeriodFrequencyType, annualNominalInterestRate, + interestCalculationPeriodMethod, + allowPartialPeriodInterestCalcualtion, principalMoney, + expectedDisbursementDate, repaymentsStartingFromDate, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, + recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, + graceOnInterestCharged, interestChargedFromDate, + inArrearsTolerance, multiDisburseLoan, emiAmount, + disbursementDatas, maxOutstandingBalance, + loanProductRelatedDetail.getGraceOnDueDate(), daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, + rescheduleStrategyMethod, + interestRecalculationCompoundingMethod, restCalendarInstance, + recalculationFrequencyType, compoundingCalendarInstance, + compoundingFrequencyType, principalThresholdForLastInstalment, + installmentAmountInMultiplesOf, + loanPreClosureInterestCalculationStrategy, loanCalendar, + approvedAmount, loanTermVariations, calendarHistoryDataWrapper, + isInterestChargedFromDateSameAsDisbursalDateEnabled, + numberOfdays, isSkipRepaymentOnFirstDayofMonth); + + } + + public static LoanApplicationTerms assembleFrom( + final LoanApplicationTerms applicationTerms, + final List loanTermVariations) { + return new LoanApplicationTerms( + applicationTerms.currency, + applicationTerms.loanTermFrequency, + applicationTerms.loanTermPeriodFrequencyType, + applicationTerms.numberOfRepayments, + applicationTerms.repaymentEvery, + applicationTerms.repaymentPeriodFrequencyType, + applicationTerms.nthDay, + applicationTerms.weekDayType, + applicationTerms.amortizationMethod, + applicationTerms.interestMethod, + applicationTerms.interestRatePerPeriod, + applicationTerms.interestRatePeriodFrequencyType, + applicationTerms.annualNominalInterestRate, + applicationTerms.interestCalculationPeriodMethod, + applicationTerms.allowPartialPeriodInterestCalcualtion, + applicationTerms.principal, + applicationTerms.expectedDisbursementDate, + applicationTerms.repaymentsStartingFromDate, + applicationTerms.calculatedRepaymentsStartingFromDate, + applicationTerms.principalGrace, + applicationTerms.recurringMoratoriumOnPrincipalPeriods, + applicationTerms.interestPaymentGrace, + applicationTerms.interestChargingGrace, + applicationTerms.interestChargedFromDate, + applicationTerms.inArrearsTolerance, + applicationTerms.multiDisburseLoan, + applicationTerms.actualFixedEmiAmount, + applicationTerms.disbursementDatas, + applicationTerms.maxOutstandingBalance, + applicationTerms.graceOnArrearsAgeing, + applicationTerms.daysInMonthType, + applicationTerms.daysInYearType, + applicationTerms.interestRecalculationEnabled, + applicationTerms.rescheduleStrategyMethod, + applicationTerms.interestRecalculationCompoundingMethod, + applicationTerms.restCalendarInstance, + applicationTerms.recalculationFrequencyType, + applicationTerms.compoundingCalendarInstance, + applicationTerms.compoundingFrequencyType, + applicationTerms.principalThresholdForLastInstalment, + applicationTerms.installmentAmountInMultiplesOf, + applicationTerms.preClosureInterestCalculationStrategy, + applicationTerms.loanCalendar, + applicationTerms.approvedPrincipal.getAmount(), + loanTermVariations, + applicationTerms.calendarHistoryDataWrapper, + applicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled, + applicationTerms.numberOfDays, + applicationTerms.isSkipRepaymentOnFirstDayOfMonth); + } + + private LoanApplicationTerms( + final ApplicationCurrency currency, + final Integer loanTermFrequency, + final PeriodFrequencyType loanTermPeriodFrequencyType, + final Integer numberOfRepayments, + final Integer repaymentEvery, + final PeriodFrequencyType repaymentPeriodFrequencyType, + final Integer nthDay, + final DayOfWeekType weekDayType, + final AmortizationMethod amortizationMethod, + final InterestMethod interestMethod, + final BigDecimal interestRatePerPeriod, + final PeriodFrequencyType interestRatePeriodFrequencyType, + final BigDecimal annualNominalInterestRate, + final InterestCalculationPeriodMethod interestCalculationPeriodMethod, + final boolean allowPartialPeriodInterestCalcualtion, + final Money principal, + final LocalDate expectedDisbursementDate, + final LocalDate repaymentsStartingFromDate, + final LocalDate calculatedRepaymentsStartingFromDate, + final Integer principalGrace, + final Integer recurringMoratoriumOnPrincipalPeriods, + final Integer interestPaymentGrace, + final Integer interestChargingGrace, + final LocalDate interestChargedFromDate, + final Money inArrearsTolerance, + final boolean multiDisburseLoan, + final BigDecimal emiAmount, + final List disbursementDatas, + final BigDecimal maxOutstandingBalance, + final Integer graceOnArrearsAgeing, + final DaysInMonthType daysInMonthType, + final DaysInYearType daysInYearType, + final boolean isInterestRecalculationEnabled, + final LoanRescheduleStrategyMethod rescheduleStrategyMethod, + final InterestRecalculationCompoundingMethod interestRecalculationCompoundingMethod, + final CalendarInstance restCalendarInstance, + final RecalculationFrequencyType recalculationFrequencyType, + final CalendarInstance compoundingCalendarInstance, + final RecalculationFrequencyType compoundingFrequencyType, + final BigDecimal principalThresholdForLastInstalment, + final Integer installmentAmountInMultiplesOf, + final LoanPreClosureInterestCalculationStrategy preClosureInterestCalculationStrategy, + final Calendar loanCalendar, BigDecimal approvedAmount, + List loanTermVariations, + final CalendarHistoryDataWrapper calendarHistoryDataWrapper, + Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, + final Integer numberOfdays, + final boolean isSkipRepaymentOnFirstDayofMonth) { + + this.currency = currency; + this.loanTermFrequency = loanTermFrequency; + this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; + this.numberOfRepayments = numberOfRepayments; + this.repaymentEvery = repaymentEvery; + this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; + this.nthDay = nthDay; + this.weekDayType = weekDayType; + this.amortizationMethod = amortizationMethod; + + this.interestMethod = interestMethod; + this.interestRatePerPeriod = interestRatePerPeriod; + this.interestRatePeriodFrequencyType = interestRatePeriodFrequencyType; + this.annualNominalInterestRate = annualNominalInterestRate; + this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; + this.allowPartialPeriodInterestCalcualtion = allowPartialPeriodInterestCalcualtion; + + this.principal = principal; + this.expectedDisbursementDate = expectedDisbursementDate; + this.repaymentsStartingFromDate = repaymentsStartingFromDate; + this.calculatedRepaymentsStartingFromDate = calculatedRepaymentsStartingFromDate; + + this.principalGrace = principalGrace; + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; + this.interestPaymentGrace = interestPaymentGrace; + this.interestChargingGrace = interestChargingGrace; + this.interestChargedFromDate = interestChargedFromDate; + + this.inArrearsTolerance = inArrearsTolerance; + this.multiDisburseLoan = multiDisburseLoan; + this.fixedEmiAmount = emiAmount; + this.actualFixedEmiAmount = emiAmount; + this.disbursementDatas = disbursementDatas; + this.maxOutstandingBalance = maxOutstandingBalance; + this.graceOnArrearsAgeing = graceOnArrearsAgeing; + this.daysInMonthType = daysInMonthType; + this.daysInYearType = daysInYearType; + this.interestRecalculationEnabled = isInterestRecalculationEnabled; + this.rescheduleStrategyMethod = rescheduleStrategyMethod; + this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; + this.restCalendarInstance = restCalendarInstance; + this.compoundingCalendarInstance = compoundingCalendarInstance; + this.recalculationFrequencyType = recalculationFrequencyType; + this.compoundingFrequencyType = compoundingFrequencyType; + this.principalThresholdForLastInstalment = principalThresholdForLastInstalment; + this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; + this.preClosureInterestCalculationStrategy = preClosureInterestCalculationStrategy; + this.isSkipRepaymentOnFirstDayOfMonth = isSkipRepaymentOnFirstDayofMonth; + this.numberOfDays = numberOfdays; + + this.loanCalendar = loanCalendar; + this.approvedPrincipal = Money.of(principal.getCurrency(), + approvedAmount); + this.variationsDataWrapper = new LoanTermVariationsDataWrapper( + loanTermVariations); + this.actualNumberOfRepayments = numberOfRepayments + + getLoanTermVariations().adjustNumberOfRepayments(); + this.adjustPrincipalForFlatLoans = principal.zero(); + if (this.calculatedRepaymentsStartingFromDate == null) { + this.seedDate = this.expectedDisbursementDate; + } else { + this.seedDate = this.calculatedRepaymentsStartingFromDate; + } + this.calendarHistoryDataWrapper = calendarHistoryDataWrapper; + this.isInterestChargedFromDateSameAsDisbursalDateEnabled = isInterestChargedFromDateSameAsDisbursalDateEnabled; + } + + public Money adjustPrincipalIfLastRepaymentPeriod( + final Money principalForPeriod, + final Money totalCumulativePrincipalToDate, final int periodNumber) { + + Money adjusted = principalForPeriod; + + final Money totalPrincipalRemaining = this.principal + .minus(totalCumulativePrincipalToDate); + if (totalPrincipalRemaining.isLessThanZero()) { + // paid too much principal, subtract amount that overpays from + // principal paid for period. + adjusted = principalForPeriod.minus(totalPrincipalRemaining.abs()); + } else if (this.actualFixedEmiAmount != null) { + final Money difference = this.principal + .minus(totalCumulativePrincipalToDate); + final Money principalThreshold = principalForPeriod.multipliedBy( + this.principalThresholdForLastInstalment).dividedBy(100, + MoneyHelper.getRoundingMode()); + if (difference.isLessThan(principalThreshold)) { + adjusted = principalForPeriod.plus(difference.abs()); + } + } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, + periodNumber)) { + + final Money difference = totalCumulativePrincipalToDate + .minus(this.principal); + if (difference.isLessThanZero()) { + adjusted = principalForPeriod.plus(difference.abs()); + } else if (difference.isGreaterThanZero()) { + adjusted = principalForPeriod.minus(difference.abs()); + } + } + + return adjusted; + } + + public Money adjustInterestIfLastRepaymentPeriod( + final Money interestForThisPeriod, + final Money totalCumulativeInterestToDate, + final Money totalInterestDueForLoan, final int periodNumber) { + + Money adjusted = interestForThisPeriod; + + final Money totalInterestRemaining = totalInterestDueForLoan + .minus(totalCumulativeInterestToDate); + if (totalInterestRemaining.isLessThanZero()) { + // paid too much interest, subtract amount that overpays from + // interest paid for period. + adjusted = interestForThisPeriod + .minus(totalInterestRemaining.abs()); + } else if (isLastRepaymentPeriod(this.actualNumberOfRepayments, + periodNumber)) { + final Money interestDifference = totalCumulativeInterestToDate + .minus(totalInterestDueForLoan); + if (interestDifference.isLessThanZero()) { + adjusted = interestForThisPeriod.plus(interestDifference.abs()); + } else if (interestDifference.isGreaterThanZero()) { + adjusted = interestForThisPeriod + .minus(interestDifference.abs()); + } + } + if (adjusted.isLessThanZero()) { + adjusted = adjusted.plus(adjusted); + } + return adjusted; + } + + /** + * Calculates the total interest to be charged on loan taking into account + * grace settings. + * + */ + public Money calculateTotalInterestCharged( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc) { + + Money totalInterestCharged = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + final Money totalInterestChargedForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( + calculator, mc); + + final Money totalInterestPerInstallment = calculateTotalInterestPerInstallmentWithoutGrace( + calculator, mc); + + final Money totalGraceOnInterestCharged = totalInterestPerInstallment + .multiplyRetainScale(getInterestChargingGrace(), + mc.getRoundingMode()); + + totalInterestCharged = totalInterestChargedForLoanTerm + .minus(totalGraceOnInterestCharged); + break; + case DECLINING_BALANCE: + case INVALID: + break; + } + + return totalInterestCharged; + } + + public Money calculateTotalPrincipalForPeriod( + final PaymentPeriodsInOneYearCalculator calculator, + final Money outstandingBalance, final int periodNumber, + final MathContext mc, Money interestForThisInstallment) { + + Money principalForInstallment = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + principalForInstallment = calculateTotalPrincipalPerPeriodWithoutGrace( + mc, periodNumber); + break; + case DECLINING_BALANCE: + switch (this.amortizationMethod) { + case EQUAL_INSTALLMENTS: + Money totalPmtForThisInstallment = pmtForInstallment( + calculator, outstandingBalance, periodNumber, mc); + principalForInstallment = calculatePrincipalDueForInstallment( + periodNumber, totalPmtForThisInstallment, + interestForThisInstallment); + break; + case EQUAL_PRINCIPAL: + principalForInstallment = calculateEqualPrincipalDueForInstallment( + mc, periodNumber); + break; + case INVALID: + break; + } + break; + case INVALID: + break; + } + + return principalForInstallment; + } + + public Money pmtForInstallment( + final PaymentPeriodsInOneYearCalculator calculator, + final Money outstandingBalance, final int periodNumber, + final MathContext mc) { + // Calculate exact period from disbursement date + final LocalDate periodStartDate = getExpectedDisbursementDate() + .withDayOfMonth(1); + final LocalDate periodEndDate = getPeriodEndDate(periodStartDate); + // equal installments + final int periodsElapsed = periodNumber - 1; + // with periodic interest for default month and year for + // equal installment + final BigDecimal periodicInterestRateForRepaymentPeriod = periodicInterestRate( + calculator, mc, DaysInMonthType.DAYS_30, + DaysInYearType.DAYS_365, periodStartDate, periodEndDate); + Money totalPmtForThisInstallment = calculateTotalDueForEqualInstallmentRepaymentPeriod( + periodicInterestRateForRepaymentPeriod, outstandingBalance, + periodsElapsed); + return totalPmtForThisInstallment; + } + + private LocalDate getPeriodEndDate(final LocalDate startDate) { + LocalDate dueRepaymentPeriodDate = startDate; + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + dueRepaymentPeriodDate = startDate.plusDays(this.repaymentEvery); + break; + case WEEKS: + dueRepaymentPeriodDate = startDate.plusWeeks(this.repaymentEvery); + break; + case MONTHS: + dueRepaymentPeriodDate = startDate.plusMonths(this.repaymentEvery); + break; + case YEARS: + dueRepaymentPeriodDate = startDate.plusYears(this.repaymentEvery); + break; + case INVALID: + break; + } + return dueRepaymentPeriodDate; + } + + public PrincipalInterest calculateTotalInterestForPeriod( + final PaymentPeriodsInOneYearCalculator calculator, + final double interestCalculationGraceOnRepaymentPeriodFraction, + final int periodNumber, final MathContext mc, + final Money cumulatingInterestPaymentDueToGrace, + final Money outstandingBalance, final LocalDate periodStartDate, + final LocalDate periodEndDate) { + + Money interestForInstallment = this.principal.zero(); + Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace + .copy(); + + switch (this.interestMethod) { + case FLAT: + + switch (this.amortizationMethod) { + case EQUAL_INSTALLMENTS: + // average out outstanding interest over remaining + // instalments where interest is applicable + interestForInstallment = calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods( + calculator, periodNumber, mc); + break; + case EQUAL_PRINCIPAL: + // interest follows time-value of money and is brought + // forward to next applicable interest payment period + final PrincipalInterest result = calculateTotalFlatInterestForPeriod( + calculator, periodNumber, mc, + interestBroughtForwardDueToGrace); + interestForInstallment = result.interest(); + interestBroughtForwardDueToGrace = result + .interestPaymentDueToGrace(); + break; + case INVALID: + break; + } + break; + case DECLINING_BALANCE: + + final Money interestForThisInstallmentBeforeGrace = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( + calculator, mc, outstandingBalance, periodStartDate, + periodEndDate); + + final Money interestForThisInstallmentAfterGrace = calculateDecliningInterestDueForInstallmentAfterApplyingGrace( + calculator, + interestCalculationGraceOnRepaymentPeriodFraction, mc, + outstandingBalance, periodNumber, periodStartDate, + periodEndDate); + + interestForInstallment = interestForThisInstallmentAfterGrace; + if (interestForThisInstallmentAfterGrace.isGreaterThanZero()) { + interestForInstallment = interestBroughtForwardDueToGrace + .plus(interestForThisInstallmentAfterGrace); + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace + .zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { + interestForInstallment = interestForThisInstallmentAfterGrace; + } else { + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace + .plus(interestForThisInstallmentBeforeGrace); + } + break; + case INVALID: + break; + } + + return new PrincipalInterest(null, interestForInstallment, + interestBroughtForwardDueToGrace); + } + + private final boolean isLastRepaymentPeriod(final int numberOfRepayments, + final int periodNumber) { + return periodNumber == numberOfRepayments; + } + + /** + * general method to calculate totalInterestDue discounting any grace + * settings + */ + private Money calculateTotalFlatInterestDueWithoutGrace( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc) { + + Money totalInterestDue = this.principal.zero(); + + switch (this.interestMethod) { + case FLAT: + final BigDecimal interestRateForLoanTerm = calculateFlatInterestRateForLoanTerm( + calculator, mc); + totalInterestDue = this.principal.multiplyRetainScale( + interestRateForLoanTerm, mc.getRoundingMode()); + + break; + case DECLINING_BALANCE: + break; + case INVALID: + break; + } + + if (this.totalInterestDue != null) { + totalInterestDue = this.totalInterestDue; + } + + return totalInterestDue; + } + + private BigDecimal calculateFlatInterestRateForLoanTerm( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc) { + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + + final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal + .valueOf(loanTermPeriodsInOneYear); + + final BigDecimal loanTermFrequencyBigDecimal = calculatePeriodsInLoanTerm(); + + return this.annualNominalInterestRate + .divide(loanTermPeriodsInYearBigDecimal, mc) + .divide(divisor, mc).multiply(loanTermFrequencyBigDecimal); + } + + private BigDecimal calculatePeriodsInLoanTerm() { + + BigDecimal periodsInLoanTerm = BigDecimal + .valueOf(this.loanTermFrequency); + switch (this.interestCalculationPeriodMethod) { + case DAILY: + // number of days from 'ideal disbursement' to final date + + LocalDate loanStartDate = getExpectedDisbursementDate(); + if (getInterestChargedFromDate() != null + && loanStartDate + .isBefore(getInterestChargedFromLocalDate())) { + loanStartDate = getInterestChargedFromLocalDate(); + } + + final int periodsInLoanTermInteger = Days.daysBetween( + loanStartDate, this.loanEndDate).getDays(); + periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); + break; + case INVALID: + break; + case SAME_AS_REPAYMENT_PERIOD: + if (this.allowPartialPeriodInterestCalcualtion) { + LocalDate startDate = getExpectedDisbursementDate(); + if (getInterestChargedFromDate() != null) { + startDate = getInterestChargedFromLocalDate(); + } + periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, + this.loanEndDate); + } + break; + } + + return periodsInLoanTerm; + } + + public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, + final LocalDate endDate) { + BigDecimal numberOfPeriods = BigDecimal.ZERO; + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + int numberOfDays = Days.daysBetween(startDate, endDate).getDays(); + numberOfPeriods = BigDecimal.valueOf((double) numberOfDays); + break; + case WEEKS: + int numberOfWeeks = Weeks.weeksBetween(startDate, endDate) + .getWeeks(); + int daysLeftAfterWeeks = Days.daysBetween( + startDate.plusWeeks(numberOfWeeks), endDate).getDays(); + numberOfPeriods = numberOfPeriods.add( + BigDecimal.valueOf(numberOfWeeks)).add( + BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); + break; + case MONTHS: + int numberOfMonths = Months.monthsBetween(startDate, endDate) + .getMonths(); + LocalDate startDateAfterConsideringMonths = startDate + .plusMonths(numberOfMonths); + LocalDate endDateAfterConsideringMonths = startDate + .plusMonths(numberOfMonths + 1); + int daysLeftAfterMonths = Days.daysBetween( + startDateAfterConsideringMonths, endDate).getDays(); + int daysInPeriodAfterMonths = Days.daysBetween( + startDateAfterConsideringMonths, + endDateAfterConsideringMonths).getDays(); + numberOfPeriods = numberOfPeriods.add( + BigDecimal.valueOf(numberOfMonths)).add( + BigDecimal.valueOf((double) daysLeftAfterMonths + / daysInPeriodAfterMonths)); + break; + case YEARS: + int numberOfYears = Years.yearsBetween(startDate, endDate) + .getYears(); + LocalDate startDateAfterConsideringYears = startDate + .plusYears(numberOfYears); + LocalDate endDateAfterConsideringYears = startDate + .plusYears(numberOfYears + 1); + int daysLeftAfterYears = Days.daysBetween( + startDateAfterConsideringYears, endDate).getDays(); + int daysInPeriodAfterYears = Days.daysBetween( + startDateAfterConsideringYears, + endDateAfterConsideringYears).getDays(); + numberOfPeriods = numberOfPeriods.add( + BigDecimal.valueOf(numberOfYears)).add( + BigDecimal.valueOf((double) daysLeftAfterYears + / daysInPeriodAfterYears)); + break; + default: + break; + } + return numberOfPeriods; + } + + public void updateLoanEndDate(final LocalDate loanEndDate) { + this.loanEndDate = loanEndDate; + } + + private Money calculateTotalInterestPerInstallmentWithoutGrace( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc) { + + final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( + calculator, mc); + + return totalInterestForLoanTerm.dividedBy( + Long.valueOf(this.actualNumberOfRepayments), + mc.getRoundingMode()); + } + + private Money calculateTotalPrincipalPerPeriodWithoutGrace( + final MathContext mc, final int periodNumber) { + final int totalRepaymentsWithCapitalPayment = calculateNumberOfRepaymentsWithPrincipalPayment(); + Money principalPerPeriod = this.principal.dividedBy( + totalRepaymentsWithCapitalPayment, mc.getRoundingMode()).plus( + this.adjustPrincipalForFlatLoans); + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principalPerPeriod = principalPerPeriod.zero(); + } + if (!isPrincipalGraceApplicableForThisPeriod(periodNumber) + && currentPeriodFixedPrincipalAmount != null) { + this.adjustPrincipalForFlatLoans = this.adjustPrincipalForFlatLoans + .plus(principalPerPeriod.minus( + currentPeriodFixedPrincipalAmount).dividedBy( + this.actualNumberOfRepayments - periodNumber, + mc.getRoundingMode())); + principalPerPeriod = this.principal.zero().plus( + currentPeriodFixedPrincipalAmount); + + } + return principalPerPeriod; + } + + private PrincipalInterest calculateTotalFlatInterestForPeriod( + final PaymentPeriodsInOneYearCalculator calculator, + final int periodNumber, final MathContext mc, + final Money cumulatingInterestPaymentDueToGrace) { + + Money interestBroughtForwardDueToGrace = cumulatingInterestPaymentDueToGrace + .copy(); + + Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace( + calculator, mc); + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace + .plus(interestForInstallment); + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isFirstPeriodAfterInterestPaymentGracePeriod(periodNumber)) { + interestForInstallment = cumulatingInterestPaymentDueToGrace + .plus(interestForInstallment); + interestBroughtForwardDueToGrace = interestBroughtForwardDueToGrace + .zero(); + } + + return new PrincipalInterest(null, interestForInstallment, + interestBroughtForwardDueToGrace); + } + + /* + * calculates the interest that should be due for a given scheduled loan + * repayment period. It takes into account GRACE periods and calculates how + * much interest is due per period by averaging the number of periods where + * interest is due and should be paid against the total known interest that + * is due without grace. + */ + private Money calculateTotalFlatInterestForInstallmentAveragingOutGracePeriods( + final PaymentPeriodsInOneYearCalculator calculator, + final int periodNumber, final MathContext mc) { + + Money interestForInstallment = calculateTotalInterestPerInstallmentWithoutGrace( + calculator, mc); + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else if (isInterestFreeGracePeriod(periodNumber)) { + interestForInstallment = interestForInstallment.zero(); + } else { + + final Money totalInterestForLoanTerm = calculateTotalFlatInterestDueWithoutGrace( + calculator, mc); + + final Money interestPerGracePeriod = calculateTotalInterestPerInstallmentWithoutGrace( + calculator, mc); + + final Money totalInterestFree = interestPerGracePeriod + .multipliedBy(getInterestChargingGrace()); + final Money realTotalInterestForLoan = totalInterestForLoanTerm + .minus(totalInterestFree); + + final Integer interestPaymentDuePeriods = calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue(this.actualNumberOfRepayments); + + interestForInstallment = realTotalInterestForLoan.dividedBy( + BigDecimal.valueOf(interestPaymentDuePeriods), + mc.getRoundingMode()); + } + + return interestForInstallment; + } + + private BigDecimal periodicInterestRate( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc, final DaysInMonthType daysInMonthType, + final DaysInYearType daysInYearType, LocalDate periodStartDate, + LocalDate periodEndDate) { + + final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal + .valueOf(loanTermPeriodsInOneYear); + + BigDecimal periodicInterestRate = BigDecimal.ZERO; + BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency( + periodStartDate, periodEndDate); + switch (this.interestCalculationPeriodMethod) { + case INVALID: + break; + case DAILY: + // For daily work out number of days in the period + BigDecimal numberOfDaysInPeriod = BigDecimal.valueOf(Days + .daysBetween(periodStartDate, periodEndDate).getDays()); + + final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate + .divide(loanTermPeriodsInYearBigDecimal, mc).divide( + divisor, mc); + + switch (this.repaymentPeriodFrequencyType) { + case INVALID: + break; + case DAYS: + periodicInterestRate = oneDayOfYearInterestRate.multiply( + numberOfDaysInPeriod, mc); + break; + case WEEKS: + periodicInterestRate = oneDayOfYearInterestRate.multiply( + numberOfDaysInPeriod, mc); + break; + case MONTHS: + if (daysInMonthType.isDaysInMonth_30()) { + numberOfDaysInPeriod = loanTermFrequencyBigDecimal + .multiply(BigDecimal.valueOf(30), mc); + } + periodicInterestRate = oneDayOfYearInterestRate.multiply( + numberOfDaysInPeriod, mc); + break; + case YEARS: + switch (daysInYearType) { + case DAYS_360: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal + .multiply(BigDecimal.valueOf(360), mc); + break; + case DAYS_364: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal + .multiply(BigDecimal.valueOf(364), mc); + break; + case DAYS_365: + numberOfDaysInPeriod = loanTermFrequencyBigDecimal + .multiply(BigDecimal.valueOf(365), mc); + break; + default: + break; + } + periodicInterestRate = oneDayOfYearInterestRate.multiply( + numberOfDaysInPeriod, mc); + break; + } + break; + case SAME_AS_REPAYMENT_PERIOD: + periodicInterestRate = this.annualNominalInterestRate + .divide(loanTermPeriodsInYearBigDecimal, mc) + .divide(divisor, mc).multiply(loanTermFrequencyBigDecimal); + break; + } + + return periodicInterestRate; + } + + private BigDecimal calculateLoanTermFrequency( + final LocalDate periodStartDate, final LocalDate periodEndDate) { + BigDecimal loanTermFrequencyBigDecimal = BigDecimal + .valueOf(this.repaymentEvery); + if (this.interestCalculationPeriodMethod.isDaily() + || this.allowPartialPeriodInterestCalcualtion) { + loanTermFrequencyBigDecimal = calculatePeriodsBetweenDates( + periodStartDate, periodEndDate); + } + return loanTermFrequencyBigDecimal; + } + + public BigDecimal interestRateFor( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc, final Money outstandingBalance, + final LocalDate fromDate, final LocalDate toDate) { + + long loanTermPeriodsInOneYear = calculator.calculate( + PeriodFrequencyType.DAYS).longValue(); + int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); + if (isFallingInRepaymentPeriod(fromDate, toDate)) { + loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); + repaymentEvery = getPeriodsBetween(fromDate, toDate); + } + + final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); + final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal + .valueOf(loanTermPeriodsInOneYear); + final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate + .divide(loanTermPeriodsInYearBigDecimal, mc) + .divide(divisor, mc); + BigDecimal interestRate = oneDayOfYearInterestRate.multiply( + BigDecimal.valueOf(repaymentEvery), mc); + return outstandingBalance.getAmount().multiply(interestRate, mc); + } + + private long calculatePeriodsInOneYear( + final PaymentPeriodsInOneYearCalculator calculator) { + + // check if daysInYears is set if so change periodsInOneYear to days set + // in db + long periodsInOneYear; + boolean daysInYearToUse = (this.repaymentPeriodFrequencyType.getCode() + .equalsIgnoreCase("periodFrequencyType.days") && !this.daysInYearType + .getCode().equalsIgnoreCase("DaysInYearType.actual")); + if (daysInYearToUse) { + periodsInOneYear = this.daysInYearType.getValue().longValue(); + } else { + periodsInOneYear = calculator.calculate( + this.repaymentPeriodFrequencyType).longValue(); + } + switch (this.interestCalculationPeriodMethod) { + case DAILY: + periodsInOneYear = (!this.daysInYearType.getCode() + .equalsIgnoreCase("DaysInYearType.actual")) ? this.daysInYearType + .getValue().longValue() : calculator.calculate( + PeriodFrequencyType.DAYS).longValue(); + break; + case INVALID: + break; + case SAME_AS_REPAYMENT_PERIOD: + break; + } + + return periodsInOneYear; + } + + private int calculateNumberOfRepaymentsWithPrincipalPayment() { + int numPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( + this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), + this.getPrincipalGrace(), 0); + return numPeriods; + } + + private Integer calculateNumberOfRepaymentPeriodsWhereInterestPaymentIsDue( + final Integer totalNumberOfRepaymentPeriods) { + return totalNumberOfRepaymentPeriods + - Math.max(getInterestChargingGrace(), + getInterestPaymentGrace()); + } + + public boolean isPrincipalGraceApplicableForThisPeriod( + final int periodNumber) { + if (this.getRecurringMoratoriumOnPrincipalPeriods() > 0) { + return ((periodNumber > 0 && periodNumber <= getPrincipalGrace()) || (periodNumber > 0 && (((periodNumber - getPrincipalGrace()) % (this + .getRecurringMoratoriumOnPrincipalPeriods() + 1)) != 1))); + } else { + return periodNumber > 0 && periodNumber <= getPrincipalGrace(); + } + } + + private boolean isInterestPaymentGraceApplicableForThisPeriod( + final int periodNumber) { + return periodNumber > 0 && periodNumber <= getInterestPaymentGrace(); + } + + private boolean isFirstPeriodAfterInterestPaymentGracePeriod( + final int periodNumber) { + return periodNumber > 0 + && periodNumber == getInterestPaymentGrace() + 1; + } + + private boolean isInterestFreeGracePeriod(final int periodNumber) { + return periodNumber > 0 && periodNumber <= getInterestChargingGrace(); + } + + public Integer getPrincipalGrace() { + Integer graceOnPrincipalPayments = Integer.valueOf(0); + if (this.principalGrace != null) { + graceOnPrincipalPayments = this.principalGrace; + } + return graceOnPrincipalPayments; + } + + public Integer getRecurringMoratoriumOnPrincipalPeriods() { + Integer recurringMoratoriumOnPrincipalPeriods = Integer.valueOf(0); + if (this.recurringMoratoriumOnPrincipalPeriods != null) { + recurringMoratoriumOnPrincipalPeriods = this.recurringMoratoriumOnPrincipalPeriods; + } + return recurringMoratoriumOnPrincipalPeriods; + } + + public Integer getInterestPaymentGrace() { + Integer graceOnInterestPayments = Integer.valueOf(0); + if (this.interestPaymentGrace != null) { + graceOnInterestPayments = this.interestPaymentGrace; + } + return graceOnInterestPayments; + } + + public Integer getInterestChargingGrace() { + Integer graceOnInterestCharged = Integer.valueOf(0); + if (this.interestChargingGrace != null) { + graceOnInterestCharged = this.interestChargingGrace; + } + return graceOnInterestCharged; + } + + private double paymentPerPeriod(final BigDecimal periodicInterestRate, + final Money balance, final int periodsElapsed) { + + if (getFixedEmiAmount() == null) { + final double futureValue = 0; + final double principalDouble = balance.getAmount() + .multiply(BigDecimal.valueOf(-1)).doubleValue(); + + final Integer periodsRemaining = calculateNumberOfRemainingPrincipalPaymentPeriods( + this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), + this.getPrincipalGrace(), periodsElapsed); + + double installmentAmount = FinanicalFunctions.pmt( + periodicInterestRate.doubleValue(), + periodsRemaining.doubleValue(), principalDouble, + futureValue, false); + + if (this.installmentAmountInMultiplesOf != null) { + installmentAmount = Money.roundToMultiplesOf(installmentAmount, + this.installmentAmountInMultiplesOf); + } + setFixedEmiAmount(BigDecimal.valueOf(installmentAmount)); + } + return getFixedEmiAmount().doubleValue(); + } + + private Money calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( + final PaymentPeriodsInOneYearCalculator calculator, + final MathContext mc, final Money outstandingBalance, + LocalDate periodStartDate, LocalDate periodEndDate) { + + Money interestDue = Money.zero(outstandingBalance.getCurrency()); + + final BigDecimal periodicInterestRate = periodicInterestRate( + calculator, mc, this.daysInMonthType, this.daysInYearType, + periodStartDate, periodEndDate); + interestDue = outstandingBalance.multiplyRetainScale( + periodicInterestRate, mc.getRoundingMode()); + + return interestDue; + } + + private Money calculateDecliningInterestDueForInstallmentAfterApplyingGrace( + final PaymentPeriodsInOneYearCalculator calculator, + final double interestCalculationGraceOnRepaymentPeriodFraction, + final MathContext mc, final Money outstandingBalance, + final int periodNumber, LocalDate periodStartDate, + LocalDate periodEndDate) { + + Money interest = calculateDecliningInterestDueForInstallmentBeforeApplyingGrace( + calculator, mc, outstandingBalance, periodStartDate, + periodEndDate); + + if (isInterestPaymentGraceApplicableForThisPeriod(periodNumber)) { + interest = interest.zero(); + } + + Double fraction = interestCalculationGraceOnRepaymentPeriodFraction; + + if (isInterestFreeGracePeriod(periodNumber)) { + interest = interest.zero(); + } else if (isInterestFreeGracePeriodFromDate(interestCalculationGraceOnRepaymentPeriodFraction)) { + + if (interestCalculationGraceOnRepaymentPeriodFraction >= Integer + .valueOf(1).doubleValue()) { + interest = interest.zero(); + fraction = fraction - Integer.valueOf(1).doubleValue(); + + } else if (interestCalculationGraceOnRepaymentPeriodFraction > Double + .valueOf("0.25") + && interestCalculationGraceOnRepaymentPeriodFraction < Integer + .valueOf(1).doubleValue()) { + + final Money graceOnInterestForRepaymentPeriod = interest + .multipliedBy(interestCalculationGraceOnRepaymentPeriodFraction); + interest = interest.minus(graceOnInterestForRepaymentPeriod); + fraction = Double.valueOf("0"); + } + } + + return interest; + } + + private boolean isInterestFreeGracePeriodFromDate( + final double interestCalculationGraceOnRepaymentPeriodFraction) { + return this.interestChargedFromDate != null + && interestCalculationGraceOnRepaymentPeriodFraction > Double + .valueOf("0.0"); + } + + private Money calculateEqualPrincipalDueForInstallment( + final MathContext mc, final int periodNumber) { + Money principal = this.principal; + if (this.fixedPrincipalAmount == null) { + final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( + this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), + this.getPrincipalGrace(), periodNumber); + principal = this.principal.dividedBy( + numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + this.fixedPrincipalAmount = principal.getAmount(); + } + principal = Money.of(getCurrency(), getFixedPrincipalAmount()); + + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principal = principal.zero(); + } + return principal; + } + + public void updateFixedPrincipalAmount(final MathContext mc, + final int periodNumber, final Money outstandingAmount) { + final Integer numberOfPrincipalPaymentPeriods = calculateNumberOfRemainingPrincipalPaymentPeriods( + this.actualNumberOfRepayments, + this.getRecurringMoratoriumOnPrincipalPeriods(), + this.getPrincipalGrace(), periodNumber - 1); + Money principal = outstandingAmount.dividedBy( + numberOfPrincipalPaymentPeriods, mc.getRoundingMode()); + this.fixedPrincipalAmount = principal.getAmount(); + } + + private static Integer calculateNumberOfRemainingPrincipalPaymentPeriods( + final Integer totalNumberOfRepaymentPeriods, + final int recurringMoratoriumOnPrincipalPeriods, + final int PrincipalGrace, int periodsElapsed) { + if (PrincipalGrace > periodsElapsed) { + periodsElapsed = PrincipalGrace; + } + Integer periodsRemaining = totalNumberOfRepaymentPeriods + - periodsElapsed; + if (recurringMoratoriumOnPrincipalPeriods > 0) { + double periodsRemainingDouble = ((double) periodsRemaining / ((double) (recurringMoratoriumOnPrincipalPeriods + 1))); + periodsRemaining = (int) Math.ceil(periodsRemainingDouble); + } + return periodsRemaining; + } + + public void setFixedPrincipalAmount(BigDecimal fixedPrincipalAmount) { + this.fixedPrincipalAmount = fixedPrincipalAmount; + } + + private Money calculatePrincipalDueForInstallment(final int periodNumber, + final Money totalDuePerInstallment, final Money periodInterest) { + + Money principal = totalDuePerInstallment.minus(periodInterest); + if (isPrincipalGraceApplicableForThisPeriod(periodNumber)) { + principal = principal.zero(); + } + return principal; + } + + private Money calculateTotalDueForEqualInstallmentRepaymentPeriod( + final BigDecimal periodicInterestRate, final Money balance, + final int periodsElapsed) { + + final double paymentPerRepaymentPeriod = paymentPerPeriod( + periodicInterestRate, balance, periodsElapsed); + + return Money.of(balance.getCurrency(), + BigDecimal.valueOf(paymentPerRepaymentPeriod)); + } + + public LoanProductRelatedDetail toLoanProductRelatedDetail() { + final MonetaryCurrency currency = new MonetaryCurrency( + this.currency.getCode(), this.currency.getDecimalPlaces(), + this.currency.getCurrencyInMultiplesOf()); + + return LoanProductRelatedDetail.createFrom(currency, + this.principal.getAmount(), this.interestRatePerPeriod, + this.interestRatePeriodFrequencyType, + this.annualNominalInterestRate, this.interestMethod, + this.interestCalculationPeriodMethod, + this.allowPartialPeriodInterestCalcualtion, + this.repaymentEvery, this.repaymentPeriodFrequencyType, + this.numberOfRepayments, this.principalGrace, + this.recurringMoratoriumOnPrincipalPeriods, + this.interestPaymentGrace, this.interestChargingGrace, + this.amortizationMethod, this.inArrearsTolerance.getAmount(), + this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), + this.daysInYearType.getValue(), + this.interestRecalculationEnabled); + } + + public Integer getLoanTermFrequency() { + return this.loanTermFrequency; + } + + public PeriodFrequencyType getLoanTermPeriodFrequencyType() { + return this.loanTermPeriodFrequencyType; + } + + public Integer getRepaymentEvery() { + return this.repaymentEvery; + } + + public PeriodFrequencyType getRepaymentPeriodFrequencyType() { + return this.repaymentPeriodFrequencyType; + } + + public Date getRepaymentStartFromDate() { + Date dateValue = null; + if (this.repaymentsStartingFromDate != null) { + dateValue = this.repaymentsStartingFromDate.toDate(); + } + return dateValue; + } + + public Date getInterestChargedFromDate() { + Date dateValue = null; + if (this.interestChargedFromDate != null) { + dateValue = this.interestChargedFromDate.toDate(); + } + return dateValue; + } + + public void setPrincipal(Money principal) { + this.principal = principal; + } + + public LocalDate getInterestChargedFromLocalDate() { + return this.interestChargedFromDate; + } + + public InterestMethod getInterestMethod() { + return this.interestMethod; + } + + public AmortizationMethod getAmortizationMethod() { + return this.amortizationMethod; + } + + public MonetaryCurrency getCurrency() { + return this.principal.getCurrency(); + } + + public Integer getNumberOfRepayments() { + return this.numberOfRepayments; + } + + public LocalDate getExpectedDisbursementDate() { + return this.expectedDisbursementDate; + } + + public LocalDate getRepaymentsStartingFromLocalDate() { + return this.repaymentsStartingFromDate; + } + + public LocalDate getCalculatedRepaymentsStartingFromLocalDate() { + return this.calculatedRepaymentsStartingFromDate; + } + + public Money getPrincipal() { + return this.principal; + } + + public Money getApprovedPrincipal() { + return this.approvedPrincipal; + } + + public List getDisbursementDatas() { + return this.disbursementDatas; + } + + public boolean isMultiDisburseLoan() { + return this.multiDisburseLoan; + } + + public Money getMaxOutstandingBalance() { + return Money.of(getCurrency(), this.maxOutstandingBalance); + } + + public BigDecimal getFixedEmiAmount() { + BigDecimal fixedEmiAmount = this.fixedEmiAmount; + if (getCurrentPeriodFixedEmiAmount() != null) { + fixedEmiAmount = getCurrentPeriodFixedEmiAmount(); + } + return fixedEmiAmount; + } + + public Integer getNthDay() { + return this.nthDay; + } + + public DayOfWeekType getWeekDayType() { + return this.weekDayType; + } + + public void setFixedEmiAmount(BigDecimal fixedEmiAmount) { + this.fixedEmiAmount = fixedEmiAmount; + } + + public void resetFixedEmiAmount() { + this.fixedEmiAmount = this.actualFixedEmiAmount; + } + + public LoanRescheduleStrategyMethod getLoanRescheduleStrategyMethod() { + return LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT; + } + + public boolean isInterestRecalculationEnabled() { + return this.interestRecalculationEnabled; + } + + public LoanRescheduleStrategyMethod getRescheduleStrategyMethod() { + return this.rescheduleStrategyMethod; + } + + public InterestRecalculationCompoundingMethod getInterestRecalculationCompoundingMethod() { + return this.interestRecalculationCompoundingMethod; + } + + public CalendarInstance getRestCalendarInstance() { + return this.restCalendarInstance; + } + + private boolean isFallingInRepaymentPeriod(LocalDate fromDate, + LocalDate toDate) { + boolean isSameAsRepaymentPeriod = false; + if (this.interestCalculationPeriodMethod.getValue().equals( + InterestCalculationPeriodMethod.SAME_AS_REPAYMENT_PERIOD + .getValue())) { + switch (this.repaymentPeriodFrequencyType) { + case WEEKS: + int days = Days.daysBetween(fromDate, toDate).getDays(); + isSameAsRepaymentPeriod = (days % 7) == 0; + break; + case MONTHS: + boolean isFromDateOnEndDate = false; + if (fromDate.getDayOfMonth() > fromDate.plusDays(1) + .getDayOfMonth()) { + isFromDateOnEndDate = true; + } + boolean isToDateOnEndDate = false; + if (toDate.getDayOfMonth() > toDate.plusDays(1).getDayOfMonth()) { + isToDateOnEndDate = true; + } + + if (isFromDateOnEndDate && isToDateOnEndDate) { + isSameAsRepaymentPeriod = true; + } else { + + int months = getPeriodsBetween(fromDate, toDate); + fromDate = fromDate.plusMonths(months); + isSameAsRepaymentPeriod = fromDate.isEqual(toDate); + } + + break; + default: + break; + } + } + return isSameAsRepaymentPeriod; + } + + private Integer getPeriodsBetween(LocalDate fromDate, LocalDate toDate) { + Integer numberOfPeriods = 0; + PeriodType periodType = PeriodType.yearMonthDay(); + Period difference = new Period(fromDate, toDate, periodType); + switch (this.repaymentPeriodFrequencyType) { + case DAYS: + numberOfPeriods = difference.getDays(); + break; + case WEEKS: + periodType = PeriodType.weeks(); + difference = new Period(fromDate, toDate, periodType); + numberOfPeriods = difference.getWeeks(); + break; + case MONTHS: + numberOfPeriods = difference.getMonths(); + break; + case YEARS: + numberOfPeriods = difference.getYears(); + break; + default: + break; + } + return numberOfPeriods; + } + + public RecalculationFrequencyType getRecalculationFrequencyType() { + return this.recalculationFrequencyType; + } + + public void updateNumberOfRepayments(final Integer numberOfRepayments) { + this.numberOfRepayments = numberOfRepayments; + this.actualNumberOfRepayments = numberOfRepayments + + getLoanTermVariations().adjustNumberOfRepayments(); + + } + + public void updatePrincipalGrace(final Integer principalGrace) { + this.principalGrace = principalGrace; + } + + public void updateInterestPaymentGrace(final Integer interestPaymentGrace) { + this.interestPaymentGrace = interestPaymentGrace; + } + + public void updateInterestRatePerPeriod(BigDecimal interestRatePerPeriod) { + if (interestRatePerPeriod != null) { + this.interestRatePerPeriod = interestRatePerPeriod; + } + } + + public void updateAnnualNominalInterestRate( + BigDecimal annualNominalInterestRate) { + if (annualNominalInterestRate != null) { + this.annualNominalInterestRate = annualNominalInterestRate; + } + } + + public BigDecimal getAnnualNominalInterestRate() { + return this.annualNominalInterestRate; + } + + public void updateInterestChargedFromDate(LocalDate interestChargedFromDate) { + if (interestChargedFromDate != null) { + this.interestChargedFromDate = interestChargedFromDate; + } + } + + public void updateLoanTermFrequency(Integer loanTermFrequency) { + if (loanTermFrequency != null) { + this.loanTermFrequency = loanTermFrequency; + } + } + + public void updateTotalInterestDue(Money totalInterestDue) { + + if (totalInterestDue != null) { + this.totalInterestDue = totalInterestDue; + } + } + + public ApplicationCurrency getApplicationCurrency() { + return this.currency; + } + + public InterestCalculationPeriodMethod getInterestCalculationPeriodMethod() { + return this.interestCalculationPeriodMethod; + } + + public LoanPreClosureInterestCalculationStrategy getPreClosureInterestCalculationStrategy() { + return this.preClosureInterestCalculationStrategy; + } + + public CalendarInstance getCompoundingCalendarInstance() { + return this.compoundingCalendarInstance; + } + + public RecalculationFrequencyType getCompoundingFrequencyType() { + return this.compoundingFrequencyType; + } + + public BigDecimal getActualFixedEmiAmount() { + return this.actualFixedEmiAmount; + } + + public Calendar getLoanCalendar() { + return loanCalendar; + } + + public BigDecimal getFixedPrincipalAmount() { + BigDecimal fixedPrincipalAmount = this.fixedPrincipalAmount; + if (getCurrentPeriodFixedPrincipalAmount() != null) { + fixedPrincipalAmount = getCurrentPeriodFixedPrincipalAmount(); + } + return fixedPrincipalAmount; + } + + public LoanTermVariationsDataWrapper getLoanTermVariations() { + return this.variationsDataWrapper; + } + + public BigDecimal getCurrentPeriodFixedEmiAmount() { + return this.currentPeriodFixedEmiAmount; + } + + public void setCurrentPeriodFixedEmiAmount( + BigDecimal currentPeriodFixedEmiAmount) { + this.currentPeriodFixedEmiAmount = currentPeriodFixedEmiAmount; + } + + public BigDecimal getCurrentPeriodFixedPrincipalAmount() { + return this.currentPeriodFixedPrincipalAmount; + } + + public void setCurrentPeriodFixedPrincipalAmount( + BigDecimal currentPeriodFixedPrincipalAmount) { + this.currentPeriodFixedPrincipalAmount = currentPeriodFixedPrincipalAmount; + } + + public Integer fetchNumberOfRepaymentsAfterExceptions() { + return this.actualNumberOfRepayments; + } + + public LocalDate getSeedDate() { + return this.seedDate; + } + + public CalendarHistoryDataWrapper getCalendarHistoryDataWrapper() { + return this.calendarHistoryDataWrapper; + } + + public Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled() { + return this.isInterestChargedFromDateSameAsDisbursalDateEnabled; + } + + public Integer getNumberOfdays() { + return numberOfDays; + } + + public boolean isSkipRepaymentOnFirstDayofMonth() { + return isSkipRepaymentOnFirstDayOfMonth; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index ee19e66cf..56248ec5f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -302,6 +302,7 @@ public class LoanScheduleAssembler { // grace details final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element); + final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element); final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element); final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element); final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element); @@ -408,7 +409,7 @@ public class LoanScheduleAssembler { repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, - calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, + calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java index 1a3da8e5e..626f84b35 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/RescheduleLoansApiConstants.java @@ -35,6 +35,7 @@ public class RescheduleLoansApiConstants { // create action request parameters public static final String loanIdParamName = "loanId"; public static final String graceOnPrincipalParamName = "graceOnPrincipal"; + public static final String recurringMoratoriumOnPrincipalPeriodsParamName = "recurringMoratoriumOnPrincipalPeriods"; public static final String graceOnInterestParamName = "graceOnInterest"; public static final String extraTermsParamName = "extraTerms"; public static final String rescheduleFromDateParamName = "rescheduleFromDate"; @@ -48,7 +49,7 @@ public class RescheduleLoansApiConstants { public static final String resheduleWithInterestRecalculationNotSupportedErrorCode = "loan.reschedule.interestrecalculation.error.code"; public static final Set CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(localeParamName, dateFormatParamName, - graceOnPrincipalParamName, graceOnInterestParamName, extraTermsParamName, rescheduleFromDateParamName, + graceOnPrincipalParamName, recurringMoratoriumOnPrincipalPeriodsParamName, graceOnInterestParamName, extraTermsParamName, rescheduleFromDateParamName, newInterestRateParamName, rescheduleReasonIdParamName, rescheduleReasonCommentParamName, submittedOnDateParamName, loanIdParamName, adjustedDueDateParamName, recalculateInterestParamName)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index f0f9a7537..48a5c516e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -51,7 +51,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "interestRateFrequencyType", "expectedDisbursementDate", - "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", + "repaymentsStartingFromDate", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index c55c16add..6b2c4c4e4 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -67,6 +67,7 @@ public final class LoanApplicationCommandFromApiJsonHelper { "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "interestRateFrequencyType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", + "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 3f0e3a17e..772ea775e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -548,7 +548,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.closedon_date as closedOnDate, cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, l.writtenoffon_date as writtenOffOnDate, " + " l.expected_firstrepaymenton_date as expectedFirstRepaymentOnDate, l.interest_calculated_from_date as interestChargedFromDate, l.expected_maturedon_date as expectedMaturityDate, " + " l.principal_amount_proposed as proposedPrincipal, l.principal_amount as principal, l.approved_principal as approvedPrincipal, l.arrearstolerance_amount as inArrearsTolerance, l.number_of_repayments as numberOfRepayments, l.repay_every as repaymentEvery," - + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing," + + " l.grace_on_principal_periods as graceOnPrincipalPayment, l.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, l.grace_on_interest_periods as graceOnInterestPayment, l.grace_interest_free_periods as graceOnInterestCharged,l.grace_on_arrears_ageing as graceOnArrearsAgeing," + " l.nominal_interest_rate_per_period as interestRatePerPeriod, l.annual_nominal_interest_rate as annualInterestRate, " + " l.repayment_period_frequency_enum as repaymentFrequencyType, l.repayment_frequency_nth_day_enum as repaymentFrequencyNthDayType, l.repayment_frequency_day_of_week_enum as repaymentFrequencyDayOfWeekType, l.interest_period_frequency_enum as interestRateFrequencyType, " + " l.term_frequency as termFrequency, l.term_period_frequency_enum as termPeriodFrequencyType, " @@ -740,6 +740,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final boolean isFloatingInterestRate = rs.getBoolean("isFloatingInterestRate"); final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment"); + final Integer recurringMoratoriumOnPrincipalPeriods = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "recurringMoratoriumOnPrincipalPeriods"); final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment"); final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged"); final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing"); @@ -899,7 +900,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { repaymentFrequencyDayOfWeekType, transactionStrategyId, transactionStrategyName, amortizationType, interestRatePerPeriod, interestRateFrequencyType, annualInterestRate, interestType, isFloatingInterestRate, interestRateDifferential, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, - expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, + expectedFirstRepaymentOnDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index 5a1db4dfe..a7d18160e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -81,7 +81,7 @@ public class LoanProductsApiResource { private final Set LOAN_PRODUCT_DATA_PARAMETERS = new HashSet<>(Arrays.asList("id", "name", "shortName", "description", "fundId", "fundName", "includeInBorrowerCycle", "currency", "principal", "minPrincipal", "maxPrincipal", "numberOfRepayments", - "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "graceOnPrincipalPayment", + "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentEvery", "repaymentFrequencyType", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "annualInterestRate", "amortizationType", "interestType", "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "inArrearsTolerance", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index f7ff94806..48f730ae2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -105,6 +105,7 @@ public class LoanProductData { private final Long transactionProcessingStrategyId; private final String transactionProcessingStrategyName; private final Integer graceOnPrincipalPayment; + private final Integer recurringMoratoriumOnPrincipalPeriods; private final Integer graceOnInterestPayment; private final Integer graceOnInterestCharged; private final Integer graceOnArrearsAgeing; @@ -218,6 +219,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId = null; final String transactionProcessingStrategyName = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; @@ -254,7 +256,7 @@ public class LoanProductData { numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName, - transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, + transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, @@ -304,6 +306,7 @@ public class LoanProductData { final Long transactionProcessingStrategyId = null; final String transactionProcessingStrategyName = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; @@ -342,7 +345,7 @@ public class LoanProductData { numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName, - transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, + transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, @@ -398,6 +401,7 @@ public class LoanProductData { final String transactionProcessingStrategyName = null; final Integer graceOnPrincipalPayment = null; + final Integer recurringMoratoriumOnPrincipalPeriods = null; final Integer graceOnInterestPayment = null; final Integer graceOnInterestCharged = null; final Integer graceOnArrearsAgeing = null; @@ -437,7 +441,7 @@ public class LoanProductData { numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName, - transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, + transactionProcessingStrategyId, transactionProcessingStrategyName, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, @@ -469,7 +473,7 @@ public class LoanProductData { final EnumOptionData interestRateFrequencyType, final EnumOptionData amortizationType, final EnumOptionData interestType, final EnumOptionData interestCalculationPeriodType, final Boolean allowPartialPeriodInterestCalcualtion, final Long fundId, final String fundName, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final Collection charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle, boolean useBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId, Collection principalVariations, @@ -499,6 +503,7 @@ public class LoanProductData { this.minNumberOfRepayments = minNumberOfRepayments; this.maxNumberOfRepayments = maxNumberOfRepayments; this.graceOnPrincipalPayment = graceOnPrincipalPayment; + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; this.graceOnInterestPayment = graceOnInterestPayment; this.graceOnInterestCharged = graceOnInterestCharged; this.repaymentEvery = repaymentEvery; @@ -668,6 +673,7 @@ public class LoanProductData { } this.graceOnPrincipalPayment = productData.graceOnPrincipalPayment; + this.recurringMoratoriumOnPrincipalPeriods = productData.recurringMoratoriumOnPrincipalPeriods; this.graceOnInterestPayment = productData.graceOnInterestPayment; this.graceOnInterestCharged = productData.graceOnInterestCharged; this.includeInBorrowerCycle = productData.includeInBorrowerCycle; @@ -810,6 +816,10 @@ public class LoanProductData { public Integer getGraceOnPrincipalPayment() { return this.graceOnPrincipalPayment; } + + public Integer getRecurringMoratoriumOnPrincipalPeriods() { + return this.recurringMoratoriumOnPrincipalPeriods; + } public Integer getGraceOnInterestPayment() { return this.graceOnInterestPayment; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 91161172d..53d60a796 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -251,6 +251,7 @@ public class LoanProduct extends AbstractPersistable { // grace details final Integer graceOnPrincipalPayment = command.integerValueOfParameterNamed("graceOnPrincipalPayment"); + final Integer recurringMoratoriumOnPrincipalPeriods = command.integerValueOfParameterNamed("recurringMoratoriumOnPrincipalPeriods"); final Integer graceOnInterestPayment = command.integerValueOfParameterNamed("graceOnInterestPayment"); final Integer graceOnInterestCharged = command.integerValueOfParameterNamed("graceOnInterestCharged"); final Integer minimumDaysBetweenDisbursalAndFirstRepayment = command @@ -327,7 +328,7 @@ public class LoanProduct extends AbstractPersistable { return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, - repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, + repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, @@ -552,7 +553,7 @@ public class LoanProduct extends AbstractPersistable { final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean considerPartialPeriodInterest, final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List charges, final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle, @@ -598,7 +599,7 @@ public class LoanProduct extends AbstractPersistable { this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, - considerPartialPeriodInterest, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, + considerPartialPeriodInterest, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), daysInYearType.getValue(), isInterestRecalculationEnabled); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java index 15cf53804..218d8394e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinimumRepaymentScheduleRelatedDetail.java @@ -40,6 +40,8 @@ public interface LoanProductMinimumRepaymentScheduleRelatedDetail { Integer graceOnPrincipalPayment(); + Integer recurringMoratoriumOnPrincipalPeriods(); + Money getInArrearsTolerance(); BigDecimal getNominalInterestRatePerPeriod(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 8f7a8d2c3..1d7aaf61f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -98,6 +98,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "grace_on_principal_periods", nullable = true) private Integer graceOnPrincipalPayment; + @Column(name = "recurring_moratorium_principal_periods", nullable = true) + private Integer recurringMoratoriumOnPrincipalPeriods; + @Column(name = "grace_on_interest_periods", nullable = true) private Integer graceOnInterestPayment; @@ -130,13 +133,13 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) { return new LoanProductRelatedDetail(currency, principal, nominalInterestRatePerPeriod, interestRatePeriodFrequencyType, nominalAnnualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, - repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, + repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } @@ -150,7 +153,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final BigDecimal defaultAnnualNominalInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final boolean allowPartialPeriodInterestCalcualtion, final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments, - final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, + final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) { this.currency = currency; @@ -165,6 +168,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.repaymentPeriodFrequencyType = repaymentFrequencyType; this.numberOfRepayments = defaultNumberOfRepayments; this.graceOnPrincipalPayment = defaultToNullIfZero(graceOnPrincipalPayment); + this.recurringMoratoriumOnPrincipalPeriods = recurringMoratoriumOnPrincipalPeriods; this.graceOnInterestPayment = defaultToNullIfZero(graceOnInterestPayment); this.graceOnInterestCharged = defaultToNullIfZero(graceOnInterestCharged); this.amortizationMethod = amortizationMethod; @@ -216,6 +220,11 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche return this.graceOnPrincipalPayment; } + @Override + public Integer recurringMoratoriumOnPrincipalPeriods() { + return this.recurringMoratoriumOnPrincipalPeriods; + } + @Override public Money getInArrearsTolerance() { return Money.of(this.currency, this.inArrearsTolerance); @@ -431,6 +440,14 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche actualChanges.put("locale", localeAsInput); this.graceOnPrincipalPayment = newValue; } + + final String recurringMoratoriumOnPrincipalPeriodsParamName = "recurringMoratoriumOnPrincipalPeriods"; + if (command.isChangeInIntegerParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName, this.recurringMoratoriumOnPrincipalPeriods)) { + final Integer newValue = command.integerValueOfParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName); + actualChanges.put(recurringMoratoriumOnPrincipalPeriodsParamName, newValue); + actualChanges.put("locale", localeAsInput); + this.recurringMoratoriumOnPrincipalPeriods = newValue; + } final String graceOnInterestPaymentParamName = "graceOnInterestPayment"; if (command.isChangeInIntegerParameterNamed(graceOnInterestPaymentParamName, this.graceOnInterestPayment)) { @@ -500,6 +517,23 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche baseDataValidator.reset().parameter("graceOnInterestCharged").value(this.graceOnInterestCharged) .failWithCode(".mustBeLessThan.numberOfRepayments"); } + + int graceOnPrincipal = 0; + if (this.getGraceOnPrincipalPayment() != null) { + graceOnPrincipal = this.getGraceOnPrincipalPayment().intValue(); + } + int recurMoratoriumOnPrincipal = 0; + if (this.recurringMoratoriumOnPrincipalPeriods() != null) { + recurMoratoriumOnPrincipal = this.recurringMoratoriumOnPrincipalPeriods().intValue(); + } + + if ( ( recurMoratoriumOnPrincipal > 0 ) + && ( (this.numberOfRepayments - graceOnPrincipal) % ( recurMoratoriumOnPrincipal + 1) != 1) ) { + baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods") + .value(graceOnPrincipal) + .value(recurMoratoriumOnPrincipal) + .failWithCode("causes.principal.moratorium.for.last.installment"); + } if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 39b4d625c..3437b9466 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -67,7 +67,7 @@ public final class LoanProductDataValidator { "numberOfRepayments", "minNumberOfRepayments", "maxNumberOfRepayments", "repaymentFrequencyType", "interestRatePerPeriod", "minInterestRatePerPeriod", "maxInterestRatePerPeriod", "interestRateFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, "inArrearsTolerance", - "transactionProcessingStrategyId", "graceOnPrincipalPayment", "graceOnInterestPayment", "graceOnInterestCharged", "charges", + "transactionProcessingStrategyId", "graceOnPrincipalPayment", "recurringMoratoriumOnPrincipalPeriods", "graceOnInterestPayment", "graceOnInterestCharged", "charges", "accountingRule", "includeInBorrowerCycle", "startDate", "closeDate", "externalId", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate", "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index c71286ada..47f54938b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -184,7 +184,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lp.nominal_interest_rate_per_period as interestRatePerPeriod, lp.min_nominal_interest_rate_per_period as minInterestRatePerPeriod, lp.max_nominal_interest_rate_per_period as maxInterestRatePerPeriod, lp.interest_period_frequency_enum as interestRatePerPeriodFreq, " + "lp.annual_nominal_interest_rate as annualInterestRate, lp.interest_method_enum as interestMethod, lp.interest_calculated_in_period_enum as interestCalculationInPeriodMethod,lp.allow_partial_period_interest_calcualtion as allowPartialPeriodInterestCalcualtion, " + "lp.repay_every as repaidEvery, lp.repayment_period_frequency_enum as repaymentPeriodFrequency, lp.number_of_repayments as numberOfRepayments, lp.min_number_of_repayments as minNumberOfRepayments, lp.max_number_of_repayments as maxNumberOfRepayments, " - + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, " + + "lp.grace_on_principal_periods as graceOnPrincipalPayment, lp.recurring_moratorium_principal_periods as recurringMoratoriumOnPrincipalPeriods, lp.grace_on_interest_periods as graceOnInterestPayment, lp.grace_interest_free_periods as graceOnInterestCharged,lp.grace_on_arrears_ageing as graceOnArrearsAgeing,lp.overdue_days_for_npa as overdueDaysForNPA, " + "lp.min_days_between_disbursal_and_first_repayment As minimumDaysBetweenDisbursalAndFirstRepayment, " + "lp.amortization_method_enum as amortizationMethod, lp.arrearstolerance_amount as tolerance, " + "lp.accounting_type as accountingType, lp.include_in_borrower_cycle as includeInBorrowerCycle,lp.use_borrower_cycle as useBorrowerCycle, lp.start_date as startDate, lp.close_date as closeDate, " @@ -263,6 +263,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer repaymentEvery = JdbcSupport.getInteger(rs, "repaidEvery"); final Integer graceOnPrincipalPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnPrincipalPayment"); + final Integer recurringMoratoriumOnPrincipalPeriods = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "recurringMoratoriumOnPrincipalPeriods"); final Integer graceOnInterestPayment = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestPayment"); final Integer graceOnInterestCharged = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnInterestCharged"); final Integer graceOnArrearsAgeing = JdbcSupport.getIntegerDefaultToNullIfZero(rs, "graceOnArrearsAgeing"); @@ -412,7 +413,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, allowPartialPeriodInterestCalcualtion, fundId, fundName, transactionStrategyId, transactionStrategyName, - graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, this.charges, accountingRuleType, + graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V298__recurring_moratorium_principal_periods.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V298__recurring_moratorium_principal_periods.sql new file mode 100644 index 000000000..ad4ddf3f6 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V298__recurring_moratorium_principal_periods.sql @@ -0,0 +1,5 @@ +ALTER TABLE m_product_loan + ADD COLUMN `recurring_moratorium_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`; + +ALTER TABLE m_loan + ADD COLUMN `recurring_moratorium_principal_periods` SMALLINT(5) NULL DEFAULT NULL AFTER `grace_on_principal_periods`; \ No newline at end of file diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java index 3e635aa78..c42957838 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java @@ -144,6 +144,7 @@ public class LoanProductRelatedDetailTestHelper { final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance) { final Integer graceOnPrincipalPayment = Integer.valueOf(0); + final Integer recurringMoratoriumOnPrincipalPeriods = Integer.valueOf(0); final Integer graceOnInterestPayment = Integer.valueOf(0); final Integer graceOnInterestCharged = Integer.valueOf(0); final Integer graceOnArrearsAgeing = Integer.valueOf(0); @@ -155,7 +156,7 @@ public class LoanProductRelatedDetailTestHelper { return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, considerPartialPeriodInterest, repayEvery, - repaymentFrequencyType, defaultNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, + repaymentFrequencyType, defaultNumberOfRepayments, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } } \ No newline at end of file