From 5ead31ae9bca79a35d90d7943668c6caf0aa9f40 Mon Sep 17 00:00:00 2001 From: Madhukar Date: Wed, 23 Jul 2014 21:41:41 +0530 Subject: [PATCH] MIFOSX-1419,MIFOSX-1420 Interest recalculation crud operations MIFOSX-1419 Update recalculation terms with product update MIFOSX-1419 Adding calendar for interest recalculation MIFOSX-1419 API docs and test case updates --- api-docs/apiLive.htm | 275 +++++++++++++++++- ...__m_loan_interest_recalculation_tables.sql | 31 ++ .../loans/LoanApplicationTestBuilder.java | 2 + .../common/loans/LoanProductTestBuilder.java | 15 +- .../portfolio/calendar/domain/Calendar.java | 11 + .../calendar/domain/CalendarEntityType.java | 9 +- .../common/service/CommonEnumerations.java | 66 +++++ .../service/DropdownReadPlatformService.java | 7 +- .../DropdownReadPlatformServiceImpl.java | 20 ++ .../loanaccount/api/LoanApiConstants.java | 10 +- .../loanaccount/api/LoansApiResource.java | 19 +- .../loanaccount/data/LoanAccountData.java | 128 ++++++-- .../data/LoanInterestRecalculationData.java | 34 +++ .../portfolio/loanaccount/domain/Loan.java | 64 ++++ .../LoanInterestRecalculationDetails.java | 63 ++++ .../domain/LoanApplicationTerms.java | 20 +- .../service/LoanScheduleAssembler.java | 13 +- ...ateLoanScheduleQueryFromApiJsonHelper.java | 18 +- ...anApplicationCommandFromApiJsonHelper.java | 20 +- ...WritePlatformServiceJpaRepositoryImpl.java | 125 ++++++++ .../service/LoanReadPlatformServiceImpl.java | 38 ++- ...WritePlatformServiceJpaRepositoryImpl.java | 24 +- .../loanproduct/LoanProductConstants.java | 11 +- .../api/LoanProductsApiResource.java | 15 +- .../loanproduct/data/LoanProductData.java | 104 ++++++- .../LoanProductInterestRecalculationData.java | 43 +++ ...nterestRecalculationCompoundingMethod.java | 50 ++++ .../InterestRecalculationPeriodMethod.java | 53 ++++ .../loanproduct/domain/LoanProduct.java | 77 ++++- ...anProductInterestRecalculationDetails.java | 93 ++++++ .../domain/LoanProductRelatedDetail.java | 54 +++- .../domain/LoanRescheduleStrategyMethod.java | 50 ++++ .../domain/RecalculationFrequencyType.java | 42 +++ .../LoanProductDataValidator.java | 87 +++++- .../LoanDropdownReadPlatformService.java | 6 + .../LoanDropdownReadPlatformServiceImpl.java | 39 +++ .../loanproduct/service/LoanEnumerations.java | 104 ++++++- .../LoanProductReadPlatformServiceImpl.java | 33 ++- ...WritePlatformServiceJpaRepositoryImpl.java | 3 + .../LoanProductRelatedDetailTestHelper.java | 8 +- 40 files changed, 1808 insertions(+), 76 deletions(-) create mode 100644 mifosng-db/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java create mode 100644 mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index 4581fe480..e47f73efd 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -6676,6 +6676,21 @@ An enumeration that indicates the type of transaction processing strategy to be + + recalculationFrequencyType + + + Frequency at which Outstanding Principal is recalculated. This field is mandatory when interest interest recalculation is enabled for product. +
Available calculation types: +
    +
  1. 1 = Same as repayment period
  2. +
  3. 2 = Daily
  4. +
  5. 3 = Weekly
  6. +
  7. 4 = Fortnightly
  8. +
  9. 5 = Monthly
  10. +
+ + @@ -6826,6 +6841,29 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1& 8 ] }, + "daysInMonthType": { + "id": 30, + "code": "DaysInMonthType.days360", + "value": "30 Days" + }, + "daysInYearType": { + "id": 360, + "code": "DaysInYearType.days360", + "value": "360 Days" + }, + "isInterestRecalculationEnabled": true, + "interestRecalculationData": { + "interestRecalculationCompoundingType": { + "id": 2, + "code": "interestRecalculationCompoundingMethod.fee", + "value": "Fee" + }, + "rescheduleStrategyType": { + "id": 2, + "code": "loanRescheduleStrategyMethod.reduce.number.of.installments", + "value": "Reduce number of installments" + } + } "charges": [], "productOptions": [ { @@ -7015,7 +7053,32 @@ GET https://DomainName/api/v1/loans/template?templateType=individual&clientId=1& "fieldOfficerId":0, "currency":{"code":"USD","name":"US Dollar","decimalPlaces":2,"displaySymbol":"$","nameCode":"currency.USD","displayLabel":"US Dollar ($)"} } - ] + ], + "interestRecalculationFrequencyTypeOptions": [{ + "id": 1, + "code": "interestRecalculationFrequencyType.same.as.repayment.period", + "value": "Same as repayment period" + }, + { + "id": 2, + "code": "interestRecalculationFrequencyType.daily", + "value": "Daily" + }, + { + "id": 3, + "code": "interestRecalculationFrequencyType.weekly", + "value": "Weekly" + }, + { + "id": 4, + "code": "interestRecalculationFrequencyType.fortnightly", + "value": "Fortnightly" + }, + { + "id": 5, + "code": "interestRecalculationFrequencyType.monthly", + "value": "Monthly" + }], } @@ -7753,6 +7816,15 @@ Request Body:
+ + + + + + + +
Additional Mandatory Fields if interest recalculation is enabled for product
recalculationFrequencyType
+
@@ -7793,6 +7865,7 @@ Request Body: "submittedOnDate": "10 Jun 2013", "linkAccountId" : "1", "fixedEmiAmount":1100, + "recalculationFrequencyType":"1", "maxOutstandingLoanBalance":"35000", "disbursementData":[{"expectedDisbursementDate":"01 November 2013", "principal":22000,"approvedPrincipal":22000}] @@ -10171,8 +10244,39 @@ An enumeration that indicates the type of transaction processing strategy to be product and the type of the accounting rule to be used Example Values:1=NONE, 2=CASH_BASED, 3=ACCRUAL_PERIODIC, 4=ACCRUAL_UPFRONT - + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Optional Fields
isInterestRecalculationEnabled
It is a flag, Used to denote whether interest recalculation is enabled or disabled for the particular product
daysInYearType
Specifies the number of days in a year.
+ Example Values:1=ACTUAL(Actual number of days in year), 360=360 DAYS, 364=364 DAYS(52 WEEKS), 365=365 DAYS
daysInMonthType
Specifies the number of days in a month.
+ Example Values:1=ACTUAL(Actual number of days in month), 30=30 DAYS
interestRecalculationCompoundingMethod
Specifies which amount portion should be added to principal for interest recalculation.
+ Example Values:0=NONE(Only on principal), 1=INTEREST(Principal+Interest), 2=FEE(Principal+Fee), 3=FEE And INTEREST (Principal+Fee+Interest)
rescheduleStrategyMethod
Specifies what action should perform on loan repayment schedule for advance payments.
+ Example Values:1=Reschedule next repayments, 2=Reduce number of installments, 3=Reduce EMI amount
@@ -10260,6 +10364,31 @@ GET https://DomainName/api/v1/loanproducts "principalVariationsForBorrowerCycle": [], "interestRateVariationsForBorrowerCycle": [], "numberOfRepaymentVariationsForBorrowerCycle": [], + "daysInMonthType": { + "id": 30, + "code": "DaysInMonthType.days360", + "value": "30 Days" + }, + "daysInYearType": { + "id": 360, + "code": "DaysInYearType.days360", + "value": "360 Days" + }, + "isInterestRecalculationEnabled": true, + "interestRecalculationData": { + "id": 3, + "productId": 1, + "interestRecalculationCompoundingType": { + "id": 2, + "code": "interestRecalculationCompoundingMethod.fee", + "value": "Fee" + }, + "rescheduleStrategyType": { + "id": 2, + "code": "loanRescheduleStrategyMethod.reduce.number.of.installments", + "value": "Reduce number of installments" + } + }, "accountingRule": { "id": 2, "code": "accountingRuleType.cash", @@ -10338,6 +10467,29 @@ GET https://DomainName/api/v1/loanproducts/template "code": "accountingRuleType.none", "value": "NONE" }, + "daysInMonthType": { + "id": 1, + "code": "DaysInMonthType.actual", + "value": "Actual" + }, + "daysInYearType": { + "id": 1, + "code": "DaysInYearType.actual", + "value": "Actual" + }, + "isInterestRecalculationEnabled": false, + "interestRecalculationData": { + "interestRecalculationCompoundingType": { + "id": 0, + "code": "interestRecalculationCompoundingMethod.none", + "value": "None" + }, + "rescheduleStrategyType": { + "id": 3, + "code": "loanRescheduleStrategyMethod.reduce.emi.amount", + "value": "Reduce EMI amount" + } + }, "paymentTypeOptions": [ { "id": 10, @@ -11018,7 +11170,72 @@ GET https://DomainName/api/v1/loanproducts/template "code": "LoanProductValueConditionType.greterthan", "value": "greter than" } - ] + ], + "daysInMonthTypeOptions": [{ + "id": 1, + "code": "DaysInMonthType.actual", + "value": "Actual" + }, + { + "id": 30, + "code": "DaysInMonthType.days360", + "value": "30 Days" + }], + "daysInYearTypeOptions": [{ + "id": 1, + "code": "DaysInYearType.actual", + "value": "Actual" + }, + { + "id": 360, + "code": "DaysInYearType.days360", + "value": "360 Days" + }, + { + "id": 364, + "code": "DaysInYearType.days364", + "value": "364 Days" + }, + { + "id": 365, + "code": "DaysInYearType.days365", + "value": "365 Days" + }], + "interestRecalculationCompoundingTypeOptions": [{ + "id": 0, + "code": "interestRecalculationCompoundingMethod.none", + "value": "None" + }, + { + "id": 2, + "code": "interestRecalculationCompoundingMethod.fee", + "value": "Fee" + }, + { + "id": 1, + "code": "interestRecalculationCompoundingMethod.interest", + "value": "Interest" + }, + { + "id": 3, + "code": "interestRecalculationCompoundingMethod.interest.and.fee", + "value": "Fee and Interest" + }], + "rescheduleStrategyTypeOptions": [{ + "id": 3, + "code": "loanRescheduleStrategyMethod.reduce.emi.amount", + "value": "Reduce EMI amount" + }, + { + "id": 2, + "code": "loanRescheduleStrategyMethod.reduce.number.of.installments", + "value": "Reduce number of installments" + }, + { + "id": 1, + "code": "loanRescheduleStrategyMethod.reschedule.next.repayments", + "value": "Reschedule next repayments" + }] } @@ -11264,8 +11481,10 @@ GET https://DomainName/api/v1/loanproducts/{productId} numberOfRepayments, repaymentEvery, repaymentFrequencyType, interestRatePerPeriod, interestRateFrequencyType, amortizationType, interestType, interestCalculationPeriodType, transactionProcessingStrategyId, - accountingRule + accountingRule, isInterestRecalculationEnabled, daysInYearType, daysInMonthType + + @@ -11278,7 +11497,8 @@ GET https://DomainName/api/v1/loanproducts/{productId} outstandingLoanBalance,overdueDaysForNPA - +
Optional Fields
+ @@ -11287,6 +11507,8 @@ GET https://DomainName/api/v1/loanproducts/{productId} incomeFromFeeAccountId, incomeFromPenaltyAccountId, writeOffAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId +
Additional Mandatory Fields for Cash(2) based accounting
+ @@ -11297,6 +11519,14 @@ GET https://DomainName/api/v1/loanproducts/{productId} receivablePenaltyAccountId, transfersInSuspenseAccountId, overpaymentLiabilityAccountId
Additional Mandatory Fields for periodic (3) and upfront (4)accrual accounting
+ + + + + + + +
Additional Mandatory Fields if interest recalculation is enabled(true)
interestRecalculationCompoundingMethod,rescheduleStrategyMethod
@@ -11356,7 +11586,10 @@ Request Body: "incomeFromFeeAccountId":8, "incomeFromPenaltyAccountId":9, "writeOffAccountId":10, -"overpaymentLiabilityAccountId":11 +"overpaymentLiabilityAccountId":11, +"daysInMonthType":1, +"daysInYearType":1, +"isInterestRecalculationEnabled":"false" } @@ -11385,6 +11618,11 @@ Request Body: "amortizationType": 1, "interestType": 0, "interestCalculationPeriodType": 1, + "daysInMonthType":1, + "daysInYearType":1, + "isInterestRecalculationEnabled":"true", + "interestRecalculationCompoundingMethod":"0", + "rescheduleStrategyMethod":"3", "accountingRule":"2", "fundSourceAccountId":"4", "loanPortfolioAccountId":"8", @@ -11445,6 +11683,9 @@ Request Body: "amortizationType": 1, "interestType": 0, "interestCalculationPeriodType": 1, + "daysInMonthType":1, + "daysInYearType":1, + "isInterestRecalculationEnabled":"false", "accountingRule":"3", "fundSourceAccountId":"4", "loanPortfolioAccountId":"8", @@ -11493,6 +11734,28 @@ Request Body: "principal": 70000, "locale": "en_GB" } +} + + +PUT loanproducts/3 +Content-Type: application/json +Request Body: +{ + "locale": "en", + "isInterestRecalculationEnabled":"true", + "interestRecalculationCompoundingMethod":"0", + "rescheduleStrategyMethod":"3", +} + + +{ + "resourceId": 1, + "changes": { + "locale": "en", + "isInterestRecalculationEnabled":"true", + "interestRecalculationCompoundingMethod":"0", + "rescheduleStrategyMethod":"3", + } }
diff --git a/mifosng-db/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql b/mifosng-db/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql new file mode 100644 index 000000000..7a9aedb33 --- /dev/null +++ b/mifosng-db/migrations/core_db/V189__m_loan_interest_recalculation_tables.sql @@ -0,0 +1,31 @@ +ALTER TABLE `m_product_loan` + ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `overdue_days_for_npa`, + ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`, + ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`; + +ALTER TABLE `m_loan` + ADD COLUMN `days_in_month_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `accrued_till`, + ADD COLUMN `days_in_year_enum` SMALLINT(5) NOT NULL DEFAULT '1' AFTER `days_in_month_enum`, + ADD COLUMN `interest_recalculation_enabled` TINYINT NOT NULL DEFAULT '0' AFTER `days_in_year_enum`; + +CREATE TABLE `m_product_loan_recalculation_details` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `product_id` BIGINT(20) NOT NULL, + `compound_type_enum` SMALLINT(5) NOT NULL, + `reschedule_strategy_enum` SMALLINT(5) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_m_product_loan_m_product_loan_recalculation_details` FOREIGN KEY (`product_id`) REFERENCES `m_product_loan` (`id`) +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; + +CREATE TABLE `m_loan_recalculation_details` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `loan_id` BIGINT(20) NOT NULL, + `compound_type_enum` SMALLINT(5) NOT NULL, + `reschedule_strategy_enum` SMALLINT(5) NOT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `FK_m_loan_m_loan_recalculation_details` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`) +) +COLLATE='latin1_swedish_ci' +ENGINE=InnoDB; diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java index f2d7a8dd9..4cbefc75d 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanApplicationTestBuilder.java @@ -37,6 +37,7 @@ public class LoanApplicationTestBuilder { private String loanType = "individual"; private String fixedEmiAmount = "10000"; private String maxOutstandingLoanBalance = "36000"; + private String recalculationFrequencyType = "1"; private String graceOnPrincipalPayment = null; private List disbursementData = null; private List charges = new ArrayList(); @@ -66,6 +67,7 @@ public class LoanApplicationTestBuilder { map.put("expectedDisbursementDate", this.expectedDisbursmentDate); map.put("submittedOnDate", this.submittedOnDate); map.put("loanType", this.loanType); + map.put("recalculationFrequencyType", this.recalculationFrequencyType); if (charges != null) { map.put("charges", charges); } diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java index 6fc65e5e0..f8e3f7df3 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/common/loans/LoanProductTestBuilder.java @@ -58,12 +58,18 @@ public class LoanProductTestBuilder { private Boolean multiDisburseLoan = false; private String outstandingLoanBalance = "35000"; private String maxTrancheCount = "35000"; + + private Boolean isInterestRecalculationEnabled = false; + private String daysInYearType = "1"; + private String daysInMonthType = "1"; + private String interestRecalculationCompoundingMethod = "0"; + private String rescheduleStrategyMethod = "1"; public String build(final String chargeId) { final HashMap map = new HashMap<>(); if (chargeId != null) { - List> charges = new ArrayList>(); + List> charges = new ArrayList<>(); HashMap chargeMap = new HashMap<>(); chargeMap.put("id", chargeId); charges.add(chargeMap); @@ -101,6 +107,13 @@ public class LoanProductTestBuilder { } else if (this.accountingRule.equals(CASH_BASED)) { map.putAll(getAccountMappingForCashBased()); } + map.put("daysInMonthType", this.daysInMonthType); + map.put("daysInYearType", this.daysInYearType); + map.put("isInterestRecalculationEnabled", this.isInterestRecalculationEnabled); + if (this.isInterestRecalculationEnabled) { + map.put("interestRecalculationCompoundingMethod", this.interestRecalculationCompoundingMethod); + map.put("rescheduleStrategyMethod", this.rescheduleStrategyMethod); + } return new Gson().toJson(map); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java index 33a590dce..4d8df2ae3 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/Calendar.java @@ -501,4 +501,15 @@ public class Calendar extends AbstractAuditableCustom { return false; } + + public void updateStartAndEndDate(final LocalDate startDate, final LocalDate endDate) { + + final CalendarFrequencyType frequencyType = CalendarUtils.getFrequency(this.recurrence); + final Integer interval = new Integer(CalendarUtils.getInterval(this.recurrence)); + final String newRecurrence = Calendar.constructRecurrence(frequencyType, interval, startDate.getDayOfWeek()); + + this.recurrence = newRecurrence; + this.startDate = startDate.toDate(); + this.endDate = endDate.toDate(); + } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java index b90bfca91..bec1bfb02 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/calendar/domain/CalendarEntityType.java @@ -10,8 +10,13 @@ import java.util.Map; public enum CalendarEntityType { - INVALID(0, "calendarEntityType.invalid"), CLIENTS(1, "calendarEntityType.clients"), GROUPS(2, "calendarEntityType.groups"), LOANS(3, - "calendarEntityType.loans"), CENTERS(4, "calendarEntityType.centers"), SAVINGS(5, "calendarEntityType.savings"); + INVALID(0, "calendarEntityType.invalid"), // + CLIENTS(1, "calendarEntityType.clients"), // + GROUPS(2, "calendarEntityType.groups"), // + LOANS(3, "calendarEntityType.loans"), // + CENTERS(4, "calendarEntityType.centers"), // + SAVINGS(5, "calendarEntityType.savings"), // + LOAN_RECALCULATION_DETAIL(6, "calendarEntityType.loan.recalculation.detail"); private final Integer value; private final String code; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/CommonEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/CommonEnumerations.java index 4e84134b2..fd0db38d3 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/CommonEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/CommonEnumerations.java @@ -5,9 +5,24 @@ import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.common.domain.ConditionType; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; public class CommonEnumerations { + + public static final String DAYS_IN_MONTH_TYPE = "daysInMonthType"; + public static final String DAYS_IN_YEAR_TYPE = "daysInYearType"; + + public static EnumOptionData commonEnumueration(final String typeName, final int id) { + EnumOptionData enumData = null; + if (typeName.equals(DAYS_IN_MONTH_TYPE)) { + enumData = daysInMonthType(id); + } else if (typeName.equals(DAYS_IN_YEAR_TYPE)) { + enumData = daysInYearType(id); + } + return enumData; + } public static EnumOptionData termFrequencyType(final int id, final String codePrefix) { return termFrequencyType(PeriodFrequencyType.fromInt(id), codePrefix); @@ -80,4 +95,55 @@ public class CommonEnumerations { return optionDatas; } + public static EnumOptionData daysInMonthType(final int id) { + return daysInMonthType(DaysInMonthType.fromInt(id)); + } + + public static EnumOptionData daysInMonthType(final DaysInMonthType type) { + EnumOptionData optionData = null; + switch (type) { + case ACTUAL: + optionData = new EnumOptionData(DaysInMonthType.ACTUAL.getValue().longValue(), DaysInMonthType.ACTUAL.getCode(), "Actual"); + break; + case DAYS_30: + optionData = new EnumOptionData(DaysInMonthType.DAYS_30.getValue().longValue(), DaysInMonthType.DAYS_30.getCode(), + "30 Days"); + break; + default: + optionData = new EnumOptionData(DaysInMonthType.INVALID.getValue().longValue(), DaysInMonthType.INVALID.getCode(), + "Invalid"); + break; + } + return optionData; + } + + public static EnumOptionData daysInYearType(final int id) { + return daysInYearType(DaysInYearType.fromInt(id)); + } + + public static EnumOptionData daysInYearType(final DaysInYearType type) { + EnumOptionData optionData = null; + switch (type) { + case ACTUAL: + optionData = new EnumOptionData(DaysInYearType.ACTUAL.getValue().longValue(), DaysInYearType.ACTUAL.getCode(), "Actual"); + break; + case DAYS_360: + optionData = new EnumOptionData(DaysInYearType.DAYS_360.getValue().longValue(), DaysInYearType.DAYS_360.getCode(), + "360 Days"); + break; + case DAYS_364: + optionData = new EnumOptionData(DaysInYearType.DAYS_364.getValue().longValue(), DaysInYearType.DAYS_364.getCode(), + "364 Days"); + break; + case DAYS_365: + optionData = new EnumOptionData(DaysInYearType.DAYS_365.getValue().longValue(), DaysInYearType.DAYS_365.getCode(), + "365 Days"); + break; + default: + optionData = new EnumOptionData(DaysInYearType.INVALID.getValue().longValue(), DaysInYearType.INVALID.getCode(), "Invalid"); + break; + } + return optionData; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformService.java index f5fafda2c..c9ede7cba 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformService.java @@ -4,10 +4,13 @@ import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; - public interface DropdownReadPlatformService { List retrievePeriodFrequencyTypeOptions(); - + List retrieveConditionTypeOptions(); + + List retrieveDaysInMonthTypeOptions(); + + List retrieveDaysInYearTypeOptions(); } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformServiceImpl.java index d85a5b3cf..2be36296a 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/common/service/DropdownReadPlatformServiceImpl.java @@ -2,12 +2,16 @@ package org.mifosplatform.portfolio.common.service; import static org.mifosplatform.portfolio.common.service.CommonEnumerations.conditionType; import static org.mifosplatform.portfolio.common.service.CommonEnumerations.termFrequencyType; +import static org.mifosplatform.portfolio.common.service.CommonEnumerations.daysInMonthType; +import static org.mifosplatform.portfolio.common.service.CommonEnumerations.daysInYearType; import java.util.Arrays; import java.util.List; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.portfolio.common.domain.ConditionType; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.springframework.stereotype.Service; @@ -30,4 +34,20 @@ public class DropdownReadPlatformServiceImpl implements DropdownReadPlatformServ return loanTermFrequencyOptions; } + @Override + public List retrieveDaysInMonthTypeOptions() { + + final List daysInMonthTypeOptions = Arrays.asList(daysInMonthType(DaysInMonthType.ACTUAL), + daysInMonthType(DaysInMonthType.DAYS_30)); + return daysInMonthTypeOptions; + } + + @Override + public List retrieveDaysInYearTypeOptions() { + + final List daysInYearTypeOptions = Arrays.asList(daysInYearType(DaysInYearType.ACTUAL), + daysInYearType(DaysInYearType.DAYS_360), daysInYearType(DaysInYearType.DAYS_364), daysInYearType(DaysInYearType.DAYS_365)); + return daysInYearTypeOptions; + } + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java index 318e0049a..dba735876 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoanApiConstants.java @@ -23,5 +23,13 @@ public interface LoanApiConstants { public static final String principalDisbursedParameterName = "transactionAmount"; public static final String ALREADY_DISBURSED = "can.not.change.disbursement.date"; - + + // Interest recalculation related + public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; + public static final String daysInYearTypeParameterName = "daysInYearType"; + public static final String daysInMonthTypeParameterName = "daysInMonthType"; + public static final String recalculationFrequencyTypeParameterName = "recalculationFrequencyType"; + public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod"; + public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java index e876dacce..07302aaec 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/api/LoansApiResource.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -50,6 +51,7 @@ import org.mifosplatform.portfolio.account.data.PortfolioAccountData; import org.mifosplatform.portfolio.account.service.AccountAssociationsReadPlatformService; import org.mifosplatform.portfolio.account.service.PortfolioAccountReadPlatformService; import org.mifosplatform.portfolio.calendar.data.CalendarData; +import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.service.CalendarReadPlatformService; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.domain.ChargeTimeType; @@ -290,7 +292,16 @@ public class LoansApiResource { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); - final LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId); + LoanAccountData loanBasicDetails = this.loanReadPlatformService.retrieveOne(loanId); + if (loanBasicDetails.isInterestRecalculationEnabled()) { + Collection interestRecalculationCalendarDatas = this.calendarReadPlatformService.retrieveCalendarsByEntity( + loanBasicDetails.getInterestRecalculationDetailId(), CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), null); + CalendarData calendarData = null; + if (!CollectionUtils.isEmpty(interestRecalculationCalendarDatas)) { + calendarData = interestRecalculationCalendarDatas.iterator().next(); + } + loanBasicDetails = LoanAccountData.withInterestRecalculationCalendarData(loanBasicDetails, calendarData); + } Collection loanRepayments = null; LoanScheduleData repaymentSchedule = null; @@ -399,6 +410,7 @@ public class LoansApiResource { Collection loanCollateralOptions = null; Collection calendarOptions = null; Collection accountLinkingOptions = null; + List interestRecalculationFrequencyTypeOptions = null; final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); if (template) { @@ -440,6 +452,9 @@ public class LoansApiResource { calendarOptions = this.loanReadPlatformService.retrieveCalendars(loanBasicDetails.groupId()); } + interestRecalculationFrequencyTypeOptions = this.dropdownReadPlatformService + .retrieveInterestRecalculationFrequencyTypeOptions(); + } Collection overdueCharges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanBasicDetails.loanProductId(), @@ -450,7 +465,7 @@ public class LoansApiResource { repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, - overdueCharges); + overdueCharges, interestRecalculationFrequencyTypeOptions); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java index 92d306e19..02de9b249 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanAccountData.java @@ -14,6 +14,7 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import javax.persistence.Transient; @@ -129,6 +130,7 @@ public class LoanAccountData { private final Collection chargeOptions; private final Collection loanCollateralOptions; private final Collection calendarOptions; + private final List interestRecalculationFrequencyTypeOptions; @Transient private final BigDecimal feeChargesAtDisbursementCharged; @@ -162,6 +164,12 @@ public class LoanAccountData { private final Collection overdueCharges; + private final EnumOptionData daysInMonthType; + private final EnumOptionData daysInYearType; + private final boolean isInterestRecalculationEnabled; + + private final LoanInterestRecalculationData interestRecalculationData; + /** * Used to produce a {@link LoanAccountData} with only collateral options * for now. @@ -248,6 +256,13 @@ public class LoanAccountData { final Boolean inArrears = null; final Boolean isNPA = null; final Collection overdueCharges = null; + + final EnumOptionData daysInMonthType = null; + final EnumOptionData daysInYearType = null; + final boolean isInterestRecalculationEnabled = false; + final LoanInterestRecalculationData interestRecalculationData = null; + final List interestRecalculationFrequencyTypeOptions = null; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, @@ -261,7 +276,8 @@ public class LoanAccountData { chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, - product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); + product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions); } /** @@ -351,6 +367,12 @@ public class LoanAccountData { final Boolean isNPA = null; final Collection overdueCharges = null; + final EnumOptionData daysInMonthType = null; + final EnumOptionData daysInYearType = null; + final boolean isInterestRecalculationEnabled = false; + final LoanInterestRecalculationData interestRecalculationData = null; + final List interestRecalculationFrequencyTypeOptions = null; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, @@ -364,7 +386,8 @@ public class LoanAccountData { loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, - isNPA); + isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, + interestRecalculationFrequencyTypeOptions); } public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { @@ -386,7 +409,8 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, - acc.isNPA); + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.interestRecalculationFrequencyTypeOptions); } /** @@ -477,6 +501,12 @@ public class LoanAccountData { final Boolean isNPA = null; final Collection overdueCharges = null; + final EnumOptionData daysInMonthType = null; + final EnumOptionData daysInYearType = null; + final boolean isInterestRecalculationEnabled = false; + final LoanInterestRecalculationData interestRecalculationData = null; + final List interestRecalculationFrequencyTypeOptions = null; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, principal, totalOverpaid, inArrearsTolerance, termFrequency, termPeriodFrequencyType, @@ -489,7 +519,8 @@ public class LoanAccountData { interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmount, maxOutstandingBalance, - emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions); } public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { @@ -510,7 +541,8 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, - acc.isNPA); + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.interestRecalculationFrequencyTypeOptions); } public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product, @@ -520,7 +552,7 @@ public class LoanAccountData { final Collection interestTypeOptions, final Collection interestCalculationPeriodTypeOptions, final Collection fundOptions, final Collection chargeOptions, final Collection loanPurposeOptions, final Collection loanCollateralOptions, - final Integer loanCycleNumber) { + final Integer loanCycleNumber, final List interestRecalculationFrequencyTypeOptions) { final Long id = null; final String accountNo = null; @@ -634,7 +666,9 @@ public class LoanAccountData { loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, product.getMultiDisburseLoan(), fixedEmi, product.getOutstandingLoanBalance(), emiAmountVariations, memberVariations, product, inArrears, - product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA); + product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), isNPA, product.getDaysInMonthType(), + product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(), + interestRecalculationFrequencyTypeOptions); } public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { @@ -668,7 +702,7 @@ public class LoanAccountData { for (final ChargeData charge : product.charges()) { charges.add(charge.toLoanChargeData()); } - + return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, acc.group, acc.loanType, product.getId(), product.getName(), product.getDescription(), product.getFundId(), product.getFundName(), acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, @@ -686,7 +720,9 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, product.getMultiDisburseLoan(), acc.fixedEmiAmount, product.getOutstandingLoanBalance(), acc.emiAmountVariations, acc.memberVariations, product, acc.inArrears, - product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA); + product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(), + product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(), + acc.interestRecalculationFrequencyTypeOptions); } /* @@ -708,7 +744,9 @@ public class LoanAccountData { final LoanSummaryData loanSummary, final BigDecimal feeChargesDueAtDisbursementCharged, final Boolean syncDisbursementWithMeeting, final Integer loancounter, final Integer loanProductCounter, final Boolean multiDisburseLoan, final BigDecimal fixedEmiAmont, final BigDecimal outstandingLoanBalance, - final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA) { + final Boolean inArrears, final Integer graceOnArrearsAgeing, final Boolean isNPA, final EnumOptionData daysInMonthType, + final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, + final LoanInterestRecalculationData interestRecalculationData) { final LoanScheduleData repaymentSchedule = null; final Collection transactions = null; @@ -739,6 +777,7 @@ public class LoanAccountData { final Map memberVariations = null; final LoanProductData product = null; final Collection overdueCharges = null; + final List interestRecalculationFrequencyTypeOptions = null; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, @@ -752,7 +791,8 @@ public class LoanAccountData { interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, loanOfficerOptions, loanPurposeOptions, loanCollateralOptions, calendarOptions, syncDisbursementWithMeeting, loancounter, loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementData, multiDisburseLoan, fixedEmiAmont, outstandingLoanBalance, - emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA); + emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, interestRecalculationFrequencyTypeOptions); } /* @@ -772,7 +812,9 @@ public class LoanAccountData { final Collection loanCollateralOptions, final Collection calendarOptions, final Collection notes, final Collection accountLinkingOptions, final PortfolioAccountData linkedAccount, final Collection disbursementDetails, - final Collection emiAmountVariations, final Collection overdueCharges) { + final Collection emiAmountVariations, final Collection overdueCharges, + final List interestRecalculationFrequencyTypeOptions) { + return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal, @@ -788,7 +830,8 @@ public class LoanAccountData { loanCollateralOptions, calendarOptions, acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, notes, accountLinkingOptions, linkedAccount, disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, - acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA); + acc.graceOnArrearsAgeing, overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, + acc.isInterestRecalculationEnabled, acc.interestRecalculationData, interestRecalculationFrequencyTypeOptions); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, @@ -799,7 +842,8 @@ public class LoanAccountData { acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions, - acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges); + acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, + acc.interestRecalculationFrequencyTypeOptions); } public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) { @@ -819,7 +863,8 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, - acc.isNPA); + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.interestRecalculationFrequencyTypeOptions); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -876,7 +921,32 @@ public class LoanAccountData { acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, acc.emiAmountVariations, memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, - acc.isNPA); + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, + acc.interestRecalculationFrequencyTypeOptions); + } + + public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) { + + final LoanInterestRecalculationData interestRecalculationData = LoanInterestRecalculationData.withCalendarData( + acc.interestRecalculationData, calendarData); + return new LoanAccountData(acc.id, acc.accountNo, acc.status, acc.externalId, acc.clientId, acc.clientName, acc.clientOfficeId, + acc.group, acc.loanType, acc.loanProductId, acc.loanProductName, acc.loanProductDescription, acc.fundId, acc.fundName, + acc.loanPurposeId, acc.loanPurposeName, acc.loanOfficerId, acc.loanOfficerName, acc.currency, acc.principal, + acc.approvedPrincipal, acc.totalOverpaid, acc.inArrearsTolerance, acc.termFrequency, acc.termPeriodFrequencyType, + acc.numberOfRepayments, acc.repaymentEvery, acc.repaymentFrequencyType, acc.transactionProcessingStrategyId, + acc.transactionProcessingStrategyName, acc.amortizationType, acc.interestRatePerPeriod, acc.interestRateFrequencyType, + acc.annualInterestRate, acc.interestType, acc.interestCalculationPeriodType, acc.expectedFirstRepaymentOnDate, + acc.graceOnPrincipalPayment, acc.graceOnInterestPayment, acc.graceOnInterestCharged, acc.interestChargedFromDate, + acc.timeline, acc.summary, acc.feeChargesAtDisbursementCharged, acc.repaymentSchedule, acc.transactions, acc.charges, + acc.collateral, acc.guarantors, acc.meeting, acc.productOptions, acc.termFrequencyTypeOptions, + acc.repaymentFrequencyTypeOptions, acc.transactionProcessingStrategyOptions, acc.interestRateFrequencyTypeOptions, + acc.amortizationTypeOptions, acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, + acc.chargeOptions, null, acc.loanOfficerOptions, acc.loanPurposeOptions, acc.loanCollateralOptions, acc.calendarOptions, + acc.syncDisbursementWithMeeting, acc.loanCounter, acc.loanProductCounter, acc.notes, acc.accountLinkingOptions, + acc.linkedAccount, acc.disbursementDetails, acc.multiDisburseLoan, acc.fixedEmiAmount, acc.maxOutstandingLoanBalance, + acc.emiAmountVariations, acc.memberVariations, acc.product, acc.inArrears, acc.graceOnArrearsAgeing, acc.overdueCharges, + acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, interestRecalculationData, + acc.interestRecalculationFrequencyTypeOptions); } private LoanAccountData( @@ -930,7 +1000,10 @@ public class LoanAccountData { final Boolean multiDisburseLoan, BigDecimal fixedEmiAmount, final BigDecimal maxOutstandingLoanBalance, final Collection emiAmountVariations, final Map memberVariations, final LoanProductData product, final Boolean inArrears, final Integer graceOnArrearsAgeing, - final Collection overdueCharges, final Boolean isNPA) { + final Collection overdueCharges, final Boolean isNPA, final EnumOptionData daysInMonthType, + final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, + final LoanInterestRecalculationData interestRecalculationData, + final List interestRecalculationFrequencyTypeOptions) { this.id = id; this.accountNo = accountNo; this.status = status; @@ -1069,6 +1142,18 @@ public class LoanAccountData { this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.overdueCharges = overdueCharges; this.isNPA = isNPA; + + this.daysInMonthType = daysInMonthType; + this.daysInYearType = daysInYearType; + this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; + this.interestRecalculationData = interestRecalculationData; + + if (CollectionUtils.isEmpty(interestRecalculationFrequencyTypeOptions)) { + this.interestRecalculationFrequencyTypeOptions = null; + } else { + this.interestRecalculationFrequencyTypeOptions = interestRecalculationFrequencyTypeOptions; + } + } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { @@ -1150,4 +1235,13 @@ public class LoanAccountData { return this.summary.getTotalOutstanding(); } + public boolean isInterestRecalculationEnabled() { + return this.isInterestRecalculationEnabled; + } + + public Long getInterestRecalculationDetailId() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getId(); } + return null; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java new file mode 100644 index 000000000..ea65516f0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/data/LoanInterestRecalculationData.java @@ -0,0 +1,34 @@ +package org.mifosplatform.portfolio.loanaccount.data; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.calendar.data.CalendarData; + +public class LoanInterestRecalculationData { + + private final Long id; + private final Long loanId; + private final EnumOptionData interestRecalculationCompoundingType; + private final EnumOptionData rescheduleStrategyType; + @SuppressWarnings("unused") + private final CalendarData calendarData; + + public LoanInterestRecalculationData(final Long id, final Long loanId, final EnumOptionData interestRecalculationCompoundingType, + final EnumOptionData rescheduleStrategyType, final CalendarData calendarData) { + this.id = id; + this.loanId = loanId; + this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; + this.rescheduleStrategyType = rescheduleStrategyType; + this.calendarData = calendarData; + } + + public static LoanInterestRecalculationData withCalendarData(final LoanInterestRecalculationData recalculationData, + final CalendarData calendarData) { + return new LoanInterestRecalculationData(recalculationData.id, recalculationData.loanId, + recalculationData.interestRecalculationCompoundingType, recalculationData.rescheduleStrategyType, calendarData); + } + + public Long getId() { + return this.id; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java index 198fb8626..7ff0dd953 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/Loan.java @@ -306,6 +306,10 @@ public class Loan extends AbstractPersistable { @Column(name = "total_recovered_derived", scale = 6, precision = 19) private BigDecimal totalRecovered; + @LazyCollection(LazyCollectionOption.FALSE) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true) + private LoanInterestRecalculationDetails loanInterestRecalculationDetails; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, @@ -1261,6 +1265,28 @@ public class Loan extends AbstractPersistable { return actualChanges; } + private boolean isInterestRecalculationEnabledForProduct() { + return this.loanProduct.isInterestRecalculationEnabled(); + } + + /** + * Update interest recalculation settings if product configuration changes + */ + public void updateLoanInterestRecalculationSettings() { + + if (isInterestRecalculationEnabledForProduct()) { + if (this.loanInterestRecalculationDetails == null) { + this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings(); + this.loanInterestRecalculationDetails.updateLoan(this); + } else { + this.loanInterestRecalculationDetails = this.loanProduct + .updateLoanInterestRecalculationDetails(this.loanInterestRecalculationDetails); + } + } else { + this.loanInterestRecalculationDetails = null; + } + } + private void recalculateLoanCharge(final LoanCharge loanCharge) { final BigDecimal amount = calculateAmountPercentageAppliedTo(loanCharge); BigDecimal chargeAmt = BigDecimal.ZERO; @@ -1483,6 +1509,15 @@ public class Loan extends AbstractPersistable { // validate if disbursement date is a holiday or a non-working day validateDisbursementDateIsOnNonWorkingDay(workingDays, allowTransactionsOnNonWorkingDay); validateDisbursementDateIsOnHoliday(allowTransactionsOnHoliday, holidays); + + /** + * Copy interest recalculation settings if interest recalculation is enabled + */ + if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()) { + this.loanInterestRecalculationDetails = this.loanProduct.copyInterestRecalculationSettings(); + this.loanInterestRecalculationDetails.updateLoan(this); + } + } private LocalDate determineExpectedMaturityDate() { @@ -3852,4 +3887,33 @@ public class Loan extends AbstractPersistable { public BigDecimal getTotalOverpaid() { return this.totalOverpaid; } + + public void updateIsInterestRecalculationEnabled() { + this.loanRepaymentScheduleDetail.updateIsInterestRecalculationEnabled(isInterestRecalculationEnabledForProduct()); + } + + public LoanInterestRecalculationDetails loanInterestRecalculationDetails() { + return this.loanInterestRecalculationDetails; + } + + public Long loanInterestRecalculationDetailId() { + if (loanInterestRecalculationDetails() != null) { return this.loanInterestRecalculationDetails.getId(); } + return null; + } + + public LocalDate getExpectedMaturityDate() { + LocalDate expectedMaturityDate = null; + if (this.expectedMaturityDate != null) { + expectedMaturityDate = new LocalDate(this.expectedMaturityDate); + } + return expectedMaturityDate; + } + + public LocalDate getMaturityDate() { + LocalDate maturityDate = getExpectedMaturityDate(); + if (this.actualMaturityDate != null) { + maturityDate = new LocalDate(this.actualMaturityDate); + } + return maturityDate; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java new file mode 100644 index 000000000..af04d5e3d --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/domain/LoanInterestRecalculationDetails.java @@ -0,0 +1,63 @@ +package org.mifosplatform.portfolio.loanaccount.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; +import org.springframework.data.jpa.domain.AbstractPersistable; + +/** + * Entity for holding interest recalculation setting, details will be copied + * from product directly + * + * @author conflux + */ + +@Entity +@Table(name = "m_loan_recalculation_details") +public class LoanInterestRecalculationDetails extends AbstractPersistable { + + @OneToOne + @JoinColumn(name = "loan_id", nullable = false) + private Loan loan; + + /** + * {@link InterestRecalculationCompoundingMethod} + */ + @Column(name = "compound_type_enum", nullable = false) + private Integer interestRecalculationCompoundingMethod; + + /** + * {@link LoanRescheduleStrategyMethod} + */ + @Column(name = "reschedule_strategy_enum", nullable = false) + private Integer rescheduleStrategyMethod; + + protected LoanInterestRecalculationDetails() { + // Default constructor for jpa repository + } + + private LoanInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod) { + this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; + this.rescheduleStrategyMethod = rescheduleStrategyMethod; + } + + public static LoanInterestRecalculationDetails createFrom(final Integer interestRecalculationCompoundingMethod, + final Integer rescheduleStrategyMethod) { + return new LoanInterestRecalculationDetails(interestRecalculationCompoundingMethod, rescheduleStrategyMethod); + } + + public void updateLoan(final Loan loan) { + this.loan = loan; + } + + public void update(final Integer interestRecalculationCompoundingMethod, final Integer rescheduleStrategyMethod) { + this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; + this.rescheduleStrategyMethod = rescheduleStrategyMethod; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index a2f5e90a4..a9d46e396 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -107,6 +107,8 @@ public final class LoanApplicationTerms { private final DaysInMonthType daysInMonthType; private final DaysInYearType daysInYearType; + + private final boolean isInterestRecalculationEnabled; public static LoanApplicationTerms assembleFrom(final ApplicationCurrency currency, final Integer loanTermFrequency, final PeriodFrequencyType loanTermPeriodFrequencyType, final Integer numberOfRepayments, final Integer repaymentEvery, @@ -120,14 +122,14 @@ public final class LoanApplicationTerms { final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, final Integer graceOnArrearsAgeing, - final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType) { + final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled) { return new LoanApplicationTerms(currency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, - maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType); + maxOutstandingBalance, emiAmountVariations, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } public static LoanApplicationTerms assembleFrom(final ApplicationCurrency applicationCurrency, final Integer loanTermFrequency, @@ -154,13 +156,18 @@ public final class LoanApplicationTerms { 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(); + return new LoanApplicationTerms(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsTolerance, multiDisburseLoan, emiAmount, disbursementDatas, maxOutstandingBalance, emiAmountVariations, loanProductRelatedDetail.getGraceOnDueDate(), - loanProductRelatedDetail.fetchDaysInMonthType(), loanProductRelatedDetail.fetchDaysInYearType()); + daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } private LoanApplicationTerms(final ApplicationCurrency currency, final Integer loanTermFrequency, @@ -174,7 +181,8 @@ public final class LoanApplicationTerms { final Integer interestChargingGrace, final LocalDate interestChargedFromDate, final Money inArrearsTolerance, final boolean multiDisburseLoan, final BigDecimal emiAmount, final List disbursementDatas, final BigDecimal maxOutstandingBalance, final List emiAmountVariations, - final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType) { + final Integer graceOnArrearsAgeing, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, + final boolean isInterestRecalculationEnabled) { this.currency = currency; this.loanTermFrequency = loanTermFrequency; this.loanTermPeriodFrequencyType = loanTermPeriodFrequencyType; @@ -208,6 +216,7 @@ public final class LoanApplicationTerms { this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.daysInMonthType = daysInMonthType; this.daysInYearType = daysInYearType; + this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; } public Money adjustPrincipalIfLastRepaymentPeriod(final Money principalForPeriod, final Money totalCumulativePrincipalToDate, @@ -752,7 +761,8 @@ public final class LoanApplicationTerms { this.interestRatePeriodFrequencyType, this.annualNominalInterestRate, this.interestMethod, this.interestCalculationPeriodMethod, this.repaymentEvery, this.repaymentPeriodFrequencyType, this.numberOfRepayments, this.principalGrace, this.interestPaymentGrace, this.interestChargingGrace, this.amortizationMethod, - this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing); + this.inArrearsTolerance.getAmount(), this.graceOnArrearsAgeing, this.daysInMonthType.getValue(), + this.daysInYearType.getValue(), this.isInterestRecalculationEnabled); } public Integer getLoanTermFrequency() { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java index 3ff5584dc..14c3081a7 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java @@ -35,6 +35,8 @@ import org.mifosplatform.portfolio.calendar.exception.MeetingFrequencyMismatchEx import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.client.domain.Client; import org.mifosplatform.portfolio.client.domain.ClientRepositoryWrapper; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.group.domain.Group; import org.mifosplatform.portfolio.group.domain.GroupRepositoryWrapper; @@ -215,13 +217,22 @@ public class LoanScheduleAssembler { LoanEnumerations.loanvariationType(LoanTermVariationType.EMI_AMOUNT), expectedDisbursementDate, emiAmount); loanVariationTermsData.add(data); + /** + * Interest recalculation settings copy from product definition + */ + final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType(); + + final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType(); + + final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); + return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, loanVariationTermsData, - graceOnArrearsAgeing, loanProduct.fetchDaysInMonthType(), loanProduct.fetchDaysInYearType()); + graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } private List fetchDisbursementData(final JsonObject command) { diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index d5c56cbb9..f9b270dff 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -33,15 +33,15 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { /** * The parameters supported for this command. */ - final Set supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", - "productId", "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", - "principal", "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", - "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", - "interestCalculationPeriodType", "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", - "graceOnInterestPayment", "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", - "dateFormat", "charges", "collateral", "syncDisbursementWithMeeting", "linkAccountId", - LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, - LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName)); + final Set supportedParameters = new HashSet<>(Arrays.asList("id", "clientId", "groupId", "loanType", "calendarId", "productId", + "accountNo", "externalId", "fundId", "loanOfficerId", "loanPurposeId", "transactionProcessingStrategyId", "principal", + "inArrearsTolerance", "interestRatePerPeriod", "repaymentEvery", "numberOfRepayments", "loanTermFrequency", + "loanTermFrequencyType", "repaymentFrequencyType", "amortizationType", "interestType", "interestCalculationPeriodType", + "expectedDisbursementDate", "repaymentsStartingFromDate", "graceOnPrincipalPayment", "graceOnInterestPayment", + "graceOnInterestCharged", "interestChargedFromDate", "submittedOnDate", "submittedOnNote", "locale", "dateFormat", "charges", + "collateral", "syncDisbursementWithMeeting", "linkAccountId", LoanApiConstants.disbursementDataParameterName, + LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, + LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.recalculationFrequencyTypeParameterName)); private final FromJsonHelper fromApiJsonHelper; diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 26dd754fc..9acbd6a7a 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.loanaccount.serialization; +import static org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants.recalculationFrequencyTypeParameterName; + import java.lang.reflect.Type; import java.math.BigDecimal; import java.util.ArrayList; @@ -59,7 +61,8 @@ public final class LoanApplicationCommandFromApiJsonHelper { "calendarId", // optional "syncDisbursementWithMeeting",// optional "linkAccountId", LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, - LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName)); + LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, + LoanApiConstants.recalculationFrequencyTypeParameterName)); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; @@ -357,6 +360,13 @@ public final class LoanApplicationCommandFromApiJsonHelper { } validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); + if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.recalculationFrequencyTypeParameterName, element)) { + final Integer recalculationFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed( + LoanApiConstants.recalculationFrequencyTypeParameterName, element); + baseDataValidator.reset().parameter(LoanApiConstants.recalculationFrequencyTypeParameterName).value(recalculationFrequencyType) + .notNull().inMinMaxRange(1, 5); + } + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } @@ -829,4 +839,12 @@ public final class LoanApplicationCommandFromApiJsonHelper { } + public void validateRecalcuationFrequencyType(final Integer recalculationFrequencyTypeValue) { + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan"); + baseDataValidator.reset().parameter(recalculationFrequencyTypeParameterName).value(recalculationFrequencyTypeValue).notNull() + .inMinMaxRange(1, 5); + if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index e73128d95..4affeedcf 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -5,6 +5,8 @@ */ package org.mifosplatform.portfolio.loanaccount.service; +import static org.mifosplatform.portfolio.loanaccount.api.LoanApiConstants.recalculationFrequencyTypeParameterName; + import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -31,9 +33,11 @@ import org.mifosplatform.portfolio.account.domain.AccountAssociationsRepository; import org.mifosplatform.portfolio.accountdetails.domain.AccountType; import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; +import org.mifosplatform.portfolio.calendar.domain.CalendarFrequencyType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.exception.CalendarNotFoundException; import org.mifosplatform.portfolio.client.domain.AccountNumberGenerator; import org.mifosplatform.portfolio.client.domain.AccountNumberGeneratorFactory; @@ -70,6 +74,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProduct; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductRepository; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; +import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.mifosplatform.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.mifosplatform.portfolio.loanproduct.serialization.LoanProductDataValidator; @@ -206,6 +211,15 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.loanRepository.save(newLoanApplication); + if (loanProduct.isInterestRecalculationEnabled()) { + + final Integer recalculationFrequencyTypeValue = command + .integerValueOfParameterNamed(recalculationFrequencyTypeParameterName); + this.fromApiJsonDeserializer.validateRecalcuationFrequencyType(recalculationFrequencyTypeValue); + + createAndPersistCalendarInstanceForInterestRecalculation(newLoanApplication, recalculationFrequencyTypeValue); + } + if (newLoanApplication.isAccountNumberRequiresAutoGeneration()) { final AccountNumberGenerator accountNoGenerator = this.accountIdentifierGeneratorFactory .determineLoanAccountNoGenerator(newLoanApplication.getId()); @@ -256,6 +270,88 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } } + private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan, final Integer recalculationFrequencyTypeValue) { + + final LocalDate calendarStartDate = loan.getDisbursementDate(); + final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + final RecalculationFrequencyType recalculationFrequencyType = RecalculationFrequencyType.fromInt(recalculationFrequencyTypeValue); + + Integer frequency = 1; + CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; + switch (recalculationFrequencyType) { + case DAILY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.DAILY; + break; + case FORTNIGHTLY: + frequency = 2; + calendarFrequencyType = CalendarFrequencyType.WEEKLY; + break; + case MONTHLY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.MONTHLY; + break; + case SAME_AS_REPAYMENT_PERIOD: + frequency = loan.repaymentScheduleDetail().getRepayEvery(); + calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType()); + break; + case WEEKLY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.WEEKLY; + break; + default: + break; + } + + final String title = "loan_recalculation_detail_" + loan.loanInterestRecalculationDetails().getId(); + final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), + calendarFrequencyType, frequency, repeatsOnDay); + final CalendarInstance calendarInstance = CalendarInstance.from(calendar, loan.loanInterestRecalculationDetails().getId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue()); + this.calendarInstanceRepository.save(calendarInstance); + + } + + private void updateCalendarDetailsForInterestRecalculation(final CalendarInstance calendarInstance, final Loan loan, + final Integer recalculationFrequencyTypeValue) { + + Calendar interestRecalculationRecurrings = calendarInstance.getCalendar(); + final LocalDate calendarStartDate = loan.getDisbursementDate(); + final Integer repeatsOnDay = calendarStartDate.getDayOfWeek(); + final RecalculationFrequencyType recalculationFrequencyType = RecalculationFrequencyType.fromInt(recalculationFrequencyTypeValue); + + Integer frequency = 1; + CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID; + switch (recalculationFrequencyType) { + case DAILY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.DAILY; + break; + case FORTNIGHTLY: + frequency = 2; + calendarFrequencyType = CalendarFrequencyType.WEEKLY; + break; + case MONTHLY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.MONTHLY; + break; + case SAME_AS_REPAYMENT_PERIOD: + frequency = loan.repaymentScheduleDetail().getRepayEvery(); + calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType()); + break; + case WEEKLY: + frequency = 1; + calendarFrequencyType = CalendarFrequencyType.WEEKLY; + break; + default: + break; + } + + interestRecalculationRecurrings.updateRepeatingCalendar(calendarStartDate, calendarFrequencyType, frequency, repeatsOnDay); + this.calendarRepository.save(interestRecalculationRecurrings); + + } + @Transactional @Override public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) { @@ -369,7 +465,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } } + existingLoanApplication.updateIsInterestRecalculationEnabled(); validateSubmittedOnDate(existingLoanApplication); + final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail(); this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(), existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(), @@ -528,6 +626,33 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } } + // updating loan interest recalculation details throwing null + // pointer exception after saveAndFlush + // http://stackoverflow.com/questions/17151757/hibernate-cascade-update-gives-null-pointer/17334374#17334374 + existingLoanApplication.updateLoanInterestRecalculationSettings(); + this.loanRepository.save(existingLoanApplication); + + if (productRelatedDetail.isInterestRecalculationEnabled()) { + + final Integer recalculationFrequencyTypeValue = command + .integerValueOfParameterNamed(recalculationFrequencyTypeParameterName); + + this.fromApiJsonDeserializer.validateRecalcuationFrequencyType(recalculationFrequencyTypeValue); + + CalendarInstance calendarInstance = this.calendarInstanceRepository.findByEntityIdAndEntityTypeIdAndCalendarTypeId( + existingLoanApplication.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); + + if (calendarInstance == null) { + createAndPersistCalendarInstanceForInterestRecalculation(existingLoanApplication, recalculationFrequencyTypeValue); + + } else { + updateCalendarDetailsForInterestRecalculation(calendarInstance, existingLoanApplication, + recalculationFrequencyTypeValue); + } + + } + return new CommandProcessingResultBuilder() // .withEntityId(loanId) // .withOfficeId(existingLoanApplication.getOfficeId()) // diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 61f6a4b3e..dd95c0331 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -50,6 +50,7 @@ import org.mifosplatform.portfolio.client.data.ClientData; import org.mifosplatform.portfolio.client.domain.ClientEnumerations; import org.mifosplatform.portfolio.client.service.ClientReadPlatformService; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.common.service.CommonEnumerations; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.group.data.GroupGeneralData; @@ -59,6 +60,7 @@ import org.mifosplatform.portfolio.group.service.SearchParameters; import org.mifosplatform.portfolio.loanaccount.data.DisbursementData; import org.mifosplatform.portfolio.loanaccount.data.LoanAccountData; import org.mifosplatform.portfolio.loanaccount.data.LoanApplicationTimelineData; +import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.mifosplatform.portfolio.loanaccount.data.LoanScheduleAccrualData; import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; import org.mifosplatform.portfolio.loanaccount.data.LoanSummaryData; @@ -499,9 +501,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " la.overdue_since_date_derived as overdueSinceDate," + " l.sync_disbursement_with_meeting as syncDisbursementWithMeeting," + " l.loan_counter as loanCounter, l.loan_product_counter as loanProductCounter," - + " l.is_npa as isNPA " + + " l.is_npa as isNPA, l.days_in_month_enum as daysInMonth, l.days_in_year_enum as daysInYear, " + + " l.interest_recalculation_enabled as isInterestRecalculationEnabled, " + + " lir.id as lirId, lir.loan_id as loanId, lir.compound_type_enum as compoundType, lir.reschedule_strategy_enum as rescheduleStrategy " + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // + + " left join m_loan_recalculation_details lir on lir.loan_id = l.id " + " join m_currency rc on rc.`code` = l.currency_code" // + " left join m_client c on c.id = l.client_id" // + " left join m_group g on g.id = l.group_id" // @@ -723,6 +728,28 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final BigDecimal fixedEmiAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "fixedEmiAmount"); final Boolean isNPA = rs.getBoolean("isNPA"); + final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth"); + final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth); + final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear"); + final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear); + final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled"); + + LoanInterestRecalculationData interestRecalculationData = null; + if (isInterestRecalculationEnabled) { + + final Long lprId = JdbcSupport.getLong(rs, "lirId"); + final Long productId = JdbcSupport.getLong(rs, "loanId"); + final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType"); + final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations + .interestRecalculationCompoundingType(compoundTypeEnumValue); + final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy"); + final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue); + final CalendarData calendarData = null; + + interestRecalculationData = new LoanInterestRecalculationData(lprId, productId, interestRecalculationCompoundingType, + rescheduleStrategyType, calendarData); + } + return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientName, clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, principal, approvedPrincipal, totalOverpaid, inArrearsTolerance, @@ -731,7 +758,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { annualInterestRate, interestType, interestCalculationPeriodType, expectedFirstRepaymentOnDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, timeline, loanSummary, feeChargesDueAtDisbursementCharged, syncDisbursementWithMeeting, loanCounter, loanProductCounter, multiDisburseLoan, - fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA); + fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, interestRecalculationData); } } @@ -1161,10 +1189,14 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { loanCycleCounter = retriveLoanCounter(clientId, loanProduct.getId()); } } + + final List interestRecalculationFrequencyTypeOptions = this.loanDropdownReadPlatformService + .retrieveInterestRecalculationFrequencyTypeOptions(); + return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, - loanCycleCounter); + loanCycleCounter, interestRecalculationFrequencyTypeOptions); } @Override diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index 6d4106a3f..eea599250 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -59,6 +59,8 @@ import org.mifosplatform.portfolio.calendar.domain.Calendar; import org.mifosplatform.portfolio.calendar.domain.CalendarEntityType; import org.mifosplatform.portfolio.calendar.domain.CalendarInstance; import org.mifosplatform.portfolio.calendar.domain.CalendarInstanceRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarRepository; +import org.mifosplatform.portfolio.calendar.domain.CalendarType; import org.mifosplatform.portfolio.calendar.service.CalendarUtils; import org.mifosplatform.portfolio.charge.domain.Charge; import org.mifosplatform.portfolio.charge.domain.ChargePaymentMode; @@ -165,6 +167,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf private final LoanReadPlatformService loanReadPlatformService; private final FromJsonHelper fromApiJsonHelper; private final AccountTransferRepository accountTransferRepository; + private final CalendarRepository calendarRepository; @Autowired public LoanWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, @@ -183,7 +186,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService, final LoanReadPlatformService loanReadPlatformService, - final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository) { + final FromJsonHelper fromApiJsonHelper, final AccountTransferRepository accountTransferRepository, + final CalendarRepository calendarRepository) { this.context = context; this.loanEventApiJsonValidator = loanEventApiJsonValidator; this.loanAssembler = loanAssembler; @@ -210,6 +214,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf this.loanReadPlatformService = loanReadPlatformService; this.fromApiJsonHelper = fromApiJsonHelper; this.accountTransferRepository = accountTransferRepository; + this.calendarRepository = calendarRepository; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -328,6 +333,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf AccountTransferType.CHARGE_PAYMENT.getValue(), null, null, null, null, null, fromSavingsAccount, isRegularTransaction); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } + + updateRecurringCalendarDatesForInterestRecalculation(loan); return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // @@ -340,6 +347,20 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .build(); } + private void updateRecurringCalendarDatesForInterestRecalculation(final Loan loan) { + + if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { + final CalendarInstance calendarInstanceForInterestRecalculation = this.calendarInstanceRepository + .findByEntityIdAndEntityTypeIdAndCalendarTypeId(loan.loanInterestRecalculationDetailId(), + CalendarEntityType.LOAN_RECALCULATION_DETAIL.getValue(), CalendarType.COLLECTION.getValue()); + + Calendar calendarForInterestRecalculation = calendarInstanceForInterestRecalculation.getCalendar(); + calendarForInterestRecalculation.updateStartAndEndDate(loan.getDisbursementDate(), loan.getMaturityDate()); + this.calendarRepository.save(calendarForInterestRecalculation); + } + + } + private void saveAndFlushLoanWithDataIntegrityViolationChecks(final Loan loan) { try { this.loanRepository.saveAndFlush(loan); @@ -503,6 +524,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf fromSavingsAccount, isRegularTransaction); this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO); } + updateRecurringCalendarDatesForInterestRecalculation(loan); } return changes; } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java index 4b2bf41e4..d3626b2ef 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/LoanProductConstants.java @@ -52,9 +52,16 @@ public interface LoanProductConstants { public static final String multiDisburseLoanParameterName = "multiDisburseLoan"; public static final String maxTrancheCountParameterName = "maxTrancheCount"; public static final String outstandingLoanBalanceParameterName = "outstandingLoanBalance"; - + public static final String graceOnArrearsAgeingParameterName = "graceOnArrearsAgeing"; - + public static final String overdueDaysForNPAParameterName = "overdueDaysForNPA"; + // Interest recalculation related + public static final String isInterestRecalculationEnabledParameterName = "isInterestRecalculationEnabled"; + public static final String daysInYearTypeParameterName = "daysInYearType"; + public static final String daysInMonthTypeParameterName = "daysInMonthType"; + public static final String interestRecalculationCompoundingMethodParameterName = "interestRecalculationCompoundingMethod"; + public static final String rescheduleStrategyMethodParameterName = "rescheduleStrategyMethod"; + } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java index 6a65d6b21..1726559ac 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -45,6 +45,7 @@ import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.organisation.monetary.service.CurrencyReadPlatformService; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; +import org.mifosplatform.portfolio.common.service.DropdownReadPlatformService; import org.mifosplatform.portfolio.fund.data.FundData; import org.mifosplatform.portfolio.fund.service.FundReadPlatformService; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; @@ -94,6 +95,7 @@ public class LoanProductsApiResource { private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService; private final DefaultToApiJsonSerializer productMixDataApiJsonSerializer; private final ProductMixReadPlatformService productMixReadPlatformService; + private final DropdownReadPlatformService commonDropdownReadPlatformService; @Autowired public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService, @@ -106,7 +108,8 @@ public class LoanProductsApiResource { final CodeValueReadPlatformService codeValueReadPlatformService, final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService, final DefaultToApiJsonSerializer productMixDataApiJsonSerializer, - final ProductMixReadPlatformService productMixReadPlatformService) { + final ProductMixReadPlatformService productMixReadPlatformService, + final DropdownReadPlatformService commonDropdownReadPlatformService) { this.context = context; this.loanProductReadPlatformService = readPlatformService; this.chargeReadPlatformService = chargeReadPlatformService; @@ -121,6 +124,7 @@ public class LoanProductsApiResource { this.accountingDropdownReadPlatformService = accountingDropdownReadPlatformService; this.productMixDataApiJsonSerializer = productMixDataApiJsonSerializer; this.productMixReadPlatformService = productMixReadPlatformService; + this.commonDropdownReadPlatformService = commonDropdownReadPlatformService; } @POST @@ -268,10 +272,17 @@ public class LoanProductsApiResource { final List loanCycleValueConditionTypeOptions = this.dropdownReadPlatformService .retrieveLoanCycleValueConditionTypeOptions(); + final List daysInMonthTypeOptions = commonDropdownReadPlatformService.retrieveDaysInMonthTypeOptions(); + final List daysInYearTypeOptions = commonDropdownReadPlatformService.retrieveDaysInYearTypeOptions(); + final List interestRecalculationCompoundingTypeOptions = dropdownReadPlatformService + .retrieveInterestRecalculationCompoundingTypeOptions(); + final List rescheduleStrategyTypeOptions = dropdownReadPlatformService.retrieveRescheduleStrategyTypeOptions(); + return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions, - accountingRuleTypeOptions, loanCycleValueConditionTypeOptions); + accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions, + interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions); } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java index 859ab526d..c50f696c6 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductData.java @@ -20,9 +20,14 @@ import org.mifosplatform.accounting.producttoaccountmapping.data.PaymentTypeToGL import org.mifosplatform.infrastructure.codes.data.CodeValueData; import org.mifosplatform.infrastructure.core.data.EnumOptionData; import org.mifosplatform.organisation.monetary.data.CurrencyData; +import org.mifosplatform.portfolio.calendar.data.CalendarData; import org.mifosplatform.portfolio.charge.data.ChargeData; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; +import org.mifosplatform.portfolio.common.service.CommonEnumerations; import org.mifosplatform.portfolio.fund.data.FundData; +import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationData; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; @@ -73,6 +78,10 @@ public class LoanProductData { private final Integer graceOnInterestCharged; private final Integer graceOnArrearsAgeing; private final Integer overdueDaysForNPA; + private final EnumOptionData daysInMonthType; + private final EnumOptionData daysInYearType; + private final boolean isInterestRecalculationEnabled; + private final LoanProductInterestRecalculationData interestRecalculationData; // charges private final Collection charges; @@ -108,6 +117,14 @@ public class LoanProductData { private final Map> accountingMappingOptions; @SuppressWarnings("unused") private final List valueConditionTypeOptions; + @SuppressWarnings("unused") + private final List daysInMonthTypeOptions; + @SuppressWarnings("unused") + private final List daysInYearTypeOptions; + @SuppressWarnings("unused") + private final List interestRecalculationCompoundingTypeOptions; + @SuppressWarnings("unused") + private final List rescheduleStrategyTypeOptions; private final Boolean multiDisburseLoan; private final Integer maxTrancheCount; @@ -160,6 +177,12 @@ public class LoanProductData { final Boolean multiDisburseLoan = null; final Integer maxTrancheCount = null; final BigDecimal outstandingLoanBalance = null; + + final EnumOptionData daysInMonthType = null; + final EnumOptionData daysInYearType = null; + final boolean isInterestRecalculationEnabled = false; + final LoanProductInterestRecalculationData interestRecalculationData = null; + return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, @@ -167,7 +190,8 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, - graceOnArrearsAgeing, overdueDaysForNPA); + graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, + interestRecalculationData); } public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { @@ -216,6 +240,11 @@ public class LoanProductData { final Integer maxTrancheCount = null; final BigDecimal outstandingLoanBalance = null; + final EnumOptionData daysInMonthType = null; + final EnumOptionData daysInYearType = null; + final boolean isInterestRecalculationEnabled = false; + final LoanProductInterestRecalculationData interestRecalculationData = null; + return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, @@ -223,7 +252,8 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariations, interestRateVariations, numberOfRepaymentVariations, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, - graceOnArrearsAgeing, overdueDaysForNPA); + graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, + interestRecalculationData); } public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { @@ -278,6 +308,12 @@ public class LoanProductData { final Integer maxTrancheCount = null; final BigDecimal outstandingLoanBalance = null; + final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(DaysInMonthType.ACTUAL); + final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(DaysInYearType.ACTUAL); + final boolean isInterestRecalculationEnabled = false; + final LoanProductInterestRecalculationData interestRecalculationData = LoanProductInterestRecalculationData + .sensibleDefaultsForNewLoanProductCreation(); + return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, @@ -285,7 +321,8 @@ public class LoanProductData { transactionProcessingStrategyName, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, charges, accountingType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, - multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA); + multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, + daysInYearType, isInterestRecalculationEnabled, interestRecalculationData); } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -313,7 +350,9 @@ public class LoanProductData { Collection principalVariations, Collection interestRateVariations, Collection numberOfRepaymentVariations, Boolean multiDisburseLoan, - Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, final Integer overdueDaysForNPA) { + Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, + final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, + final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData) { this.id = id; this.name = name; this.shortName = shortName; @@ -379,6 +418,15 @@ public class LoanProductData { this.maxTrancheCount = maxTrancheCount; this.graceOnArrearsAgeing = graceOnArrearsAgeing; this.overdueDaysForNPA = overdueDaysForNPA; + this.daysInMonthType = daysInMonthType; + this.daysInYearType = daysInYearType; + this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; + this.interestRecalculationData = interestRecalculationData; + + this.daysInMonthTypeOptions = null; + this.daysInYearTypeOptions = null; + this.interestRecalculationCompoundingTypeOptions = null; + this.rescheduleStrategyTypeOptions = null; } public LoanProductData(final LoanProductData productData, final Collection chargeOptions, @@ -388,7 +436,9 @@ public class LoanProductData { final List repaymentFrequencyTypeOptions, final List interestRateFrequencyTypeOptions, final Collection fundOptions, final Collection transactionStrategyOptions, final Map> accountingMappingOptions, final List accountingRuleOptions, - final List valueConditionTypeOptions) { + final List valueConditionTypeOptions, final List daysInMonthTypeOptions, + final List daysInYearTypeOptions, final List interestRecalculationCompoundingTypeOptions, + final List rescheduleStrategyTypeOptions) { this.id = productData.id; this.name = productData.name; this.shortName = productData.shortName; @@ -466,6 +516,16 @@ public class LoanProductData { this.graceOnArrearsAgeing = productData.graceOnArrearsAgeing; this.overdueDaysForNPA = productData.overdueDaysForNPA; + this.daysInMonthType = productData.daysInMonthType; + this.daysInYearType = productData.daysInYearType; + this.isInterestRecalculationEnabled = productData.isInterestRecalculationEnabled; + this.interestRecalculationData = productData.interestRecalculationData; + + this.daysInMonthTypeOptions = daysInMonthTypeOptions; + this.daysInYearTypeOptions = daysInYearTypeOptions; + this.interestRecalculationCompoundingTypeOptions = interestRecalculationCompoundingTypeOptions; + this.rescheduleStrategyTypeOptions = rescheduleStrategyTypeOptions; + } private Collection nullIfEmpty(final Collection charges) { @@ -655,6 +715,22 @@ public class LoanProductData { return this.graceOnArrearsAgeing; } + public EnumOptionData getDaysInMonthType() { + return this.daysInMonthType; + } + + public EnumOptionData getDaysInYearType() { + return this.daysInYearType; + } + + public boolean isInterestRecalculationEnabled() { + return this.isInterestRecalculationEnabled; + } + + public LoanProductInterestRecalculationData getInterestRecalculationData() { + return this.interestRecalculationData; + } + public Collection overdueFeeCharges() { Collection overdueFeeCharges = new ArrayList<>(); Collection charges = charges(); @@ -665,4 +741,22 @@ public class LoanProductData { } return overdueFeeCharges; } + + public LoanInterestRecalculationData toLoanInterestRecalculationData() { + final Long id = null; + final Long loanId = null; + final CalendarData calendarData = null; + return new LoanInterestRecalculationData(id, loanId, getInterestRecalculationCompoundingType(), getRescheduleStrategyType(), + calendarData); + } + + private EnumOptionData getRescheduleStrategyType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getRescheduleStrategyType(); } + return null; + } + + private EnumOptionData getInterestRecalculationCompoundingType() { + if (isInterestRecalculationEnabled()) { return this.interestRecalculationData.getInterestRecalculationCompoundingType(); } + return null; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java new file mode 100644 index 000000000..f9bdde038 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/data/LoanProductInterestRecalculationData.java @@ -0,0 +1,43 @@ +package org.mifosplatform.portfolio.loanproduct.data; + +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType; + +import org.mifosplatform.infrastructure.core.data.EnumOptionData; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; + +public class LoanProductInterestRecalculationData { + + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final Long productId; + private final EnumOptionData interestRecalculationCompoundingType; + private final EnumOptionData rescheduleStrategyType; + + public LoanProductInterestRecalculationData(final Long id, final Long productId, + final EnumOptionData interestRecalculationCompoundingType, final EnumOptionData rescheduleStrategyType) { + this.id = id; + this.productId = productId; + this.interestRecalculationCompoundingType = interestRecalculationCompoundingType; + this.rescheduleStrategyType = rescheduleStrategyType; + } + + public static LoanProductInterestRecalculationData sensibleDefaultsForNewLoanProductCreation() { + final Long id = null; + final Long productId = null; + final EnumOptionData interestRecalculationCompoundingType = interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE); + final EnumOptionData rescheduleStrategyType = rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT); + return new LoanProductInterestRecalculationData(id, productId, interestRecalculationCompoundingType, rescheduleStrategyType); + } + + public EnumOptionData getInterestRecalculationCompoundingType() { + return this.interestRecalculationCompoundingType; + } + + public EnumOptionData getRescheduleStrategyType() { + return this.rescheduleStrategyType; + } + +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java new file mode 100644 index 000000000..8cf5593c0 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationCompoundingMethod.java @@ -0,0 +1,50 @@ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.util.HashMap; +import java.util.Map; + +/*** + *
    + * People typically use either of the following settings when defining interest + * recalculation method: + *
  • NONE
  • + *
  • INTEREST
  • + *
  • FEE
  • + *
  • INTEREST_AND_FEE
  • + *
+ */ +public enum InterestRecalculationCompoundingMethod { + + NONE(0, "interestRecalculationCompoundingMethod.none"), // + INTEREST(1, "interestRecalculationCompoundingMethod.interest"), // + FEE(2, "interestRecalculationCompoundingMethod.fee"), // + INTEREST_AND_FEE(3, "interestRecalculationCompoundingMethod.interest.and.fee"); + + private final Integer value; + private final String code; + + private static final Map intToEnumMap = new HashMap<>(); + static { + for (final InterestRecalculationCompoundingMethod type : InterestRecalculationCompoundingMethod.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static InterestRecalculationCompoundingMethod fromInt(final Integer ruleTypeValue) { + final InterestRecalculationCompoundingMethod type = intToEnumMap.get(ruleTypeValue); + return type; + } + + private InterestRecalculationCompoundingMethod(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java new file mode 100644 index 000000000..19b16bb73 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/InterestRecalculationPeriodMethod.java @@ -0,0 +1,53 @@ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.util.HashMap; +import java.util.Map; + +/*** + *
    + * People typically use either of the following settings when defining interest + * recalculation method: + *
  • SAME_AS_REPAYMENT_PERIOD
  • + *
  • DAILY
  • + *
  • WEEKLY
  • + *
  • FORTNIGHTLY
  • + *
  • MONTHLY
  • + *
+ */ + +public enum InterestRecalculationPeriodMethod { + INVALID(0, "interestRecalculationPeriodMethod.invalid"), // + DAILY(1, "interestRecalculationPeriodMethod.daily"), // + WEEKLY(2, "interestRecalculationPeriodMethod.weekly"), // + FORTNIGHTLY(3, "interestRecalculationPeriodMethod.fortnightly"), // + MONTHLY(4, "interestRecalculationPeriodMethod.monthly"), // + SAME_AS_REPAYMENT_PERIOD(5, "interestRecalculationPeriodMethod.same.as.repayment.period"); + + private final Integer value; + private final String code; + private static final Map intToEnumMap = new HashMap<>(); + + static { + for (final InterestRecalculationPeriodMethod type : InterestRecalculationPeriodMethod.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static InterestRecalculationPeriodMethod fromInt(final Integer ruleTypeValue) { + final InterestRecalculationPeriodMethod type = intToEnumMap.get(ruleTypeValue); + return type; + } + + private InterestRecalculationPeriodMethod(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java index 7e6951cb7..da0eea1d2 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProduct.java @@ -24,6 +24,7 @@ import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.OneToOne; import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; @@ -42,6 +43,7 @@ import org.mifosplatform.portfolio.common.domain.DaysInMonthType; import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.fund.domain.Fund; +import org.mifosplatform.portfolio.loanaccount.domain.LoanInterestRecalculationDetails; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.springframework.data.jpa.domain.AbstractPersistable; @@ -121,6 +123,10 @@ public class LoanProduct extends AbstractPersistable { @Column(name = "overdue_days_for_npa", nullable = true) private Integer overdueDaysForNPA; + @LazyCollection(LazyCollectionOption.FALSE) + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true) + private LoanProductInterestRecalculationDetails productInterestRecalculationDetails; + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, final List productCharges, final JsonCommand command, final AprCalculator aprCalculator) { @@ -187,13 +193,33 @@ public class LoanProduct extends AbstractPersistable { final Integer overdueDaysForNPA = command.integerValueOfParameterNamed(LoanProductConstants.overdueDaysForNPAParameterName); + // Interest recalculation settings + final boolean isInterestRecalculationEnabled = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName); + final DaysInMonthType daysInMonthType = DaysInMonthType.fromInt(command + .integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName)); + + final DaysInYearType daysInYearType = DaysInYearType.fromInt(command + .integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName)); + + LoanProductInterestRecalculationDetails interestRecalculationSettings = null; + + if (isInterestRecalculationEnabled) { + interestRecalculationSettings = LoanProductInterestRecalculationDetails.createFrom(command); + } return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, repaymentEvery, repaymentFrequencyType, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, inArrearsTolerance, productCharges, accountingRuleType, includeInBorrowerCycle, startDate, closeDate, externalId, useBorrowerCycle, loanProductBorrowerCycleVariations, multiDisburseLoan, maxTrancheCount, - outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA); + outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, + isInterestRecalculationEnabled, interestRecalculationSettings); + + } + + public void updateLoanProductForInterestRecalculationDetails() { + this.productInterestRecalculationDetails.updateProduct(this); } /** @@ -404,7 +430,8 @@ public class LoanProduct extends AbstractPersistable { final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle, final Set loanProductBorrowerCycleVariations, final boolean multiDisburseLoan, final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, - final Integer overdueDaysForNPA) { + final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, + final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -422,7 +449,8 @@ public class LoanProduct extends AbstractPersistable { this.loanProductRelatedDetail = new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfInstallments, graceOnPrincipalPayment, graceOnInterestPayment, - graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing); + graceOnInterestCharged, amortizationMethod, inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType.getValue(), + daysInYearType.getValue(), isInterestRecalculationEnabled); this.loanProductRelatedDetail.validateRepaymentPeriodWithGraceSettings(); @@ -451,6 +479,7 @@ public class LoanProduct extends AbstractPersistable { } this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance); this.overdueDaysForNPA = overdueDaysForNPA; + this.productInterestRecalculationDetails = productInterestRecalculationDetails; } public MonetaryCurrency getCurrency() { @@ -622,6 +651,27 @@ public class LoanProduct extends AbstractPersistable { this.overdueDaysForNPA = newValue; } + /** + * Update interest recalculation settings + */ + final boolean isInterestRecalculationEnabledChanged = actualChanges + .containsKey(LoanProductConstants.isInterestRecalculationEnabledParameterName); + + if (isInterestRecalculationEnabledChanged) { + if (this.isInterestRecalculationEnabled()) { + this.productInterestRecalculationDetails = LoanProductInterestRecalculationDetails.createFrom(command); + this.productInterestRecalculationDetails.updateProduct(this); + actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, + command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)); + actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName, + command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName)); + } else { + this.productInterestRecalculationDetails = null; + } + } else { + this.productInterestRecalculationDetails.update(command, actualChanges, localeAsInput); + } + return actualChanges; } @@ -738,6 +788,10 @@ public class LoanProduct extends AbstractPersistable { return this.loanProducTrancheDetails.maxTrancheCount(); } + public boolean isInterestRecalculationEnabled() { + return this.loanProductRelatedDetail.isInterestRecalculationEnabled(); + } + public LoanProductBorrowerCycleVariations fetchLoanProductBorrowerCycleVariationById(Long id) { LoanProductBorrowerCycleVariations borrowerCycleVariation = null; for (LoanProductBorrowerCycleVariations cycleVariation : this.borrowerCycleVariations) { @@ -850,4 +904,21 @@ public class LoanProduct extends AbstractPersistable { return this.loanProductRelatedDetail.fetchDaysInYearType(); } + public LoanInterestRecalculationDetails copyInterestRecalculationSettings() { + LoanInterestRecalculationDetails loanInterestRecalculationDetails = null; + if (this.isInterestRecalculationEnabled()) { + loanInterestRecalculationDetails = LoanInterestRecalculationDetails.createFrom( + this.productInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(), + this.productInterestRecalculationDetails.getRescheduleStrategyMethod()); + } + return loanInterestRecalculationDetails; + } + + public LoanInterestRecalculationDetails updateLoanInterestRecalculationDetails( + LoanInterestRecalculationDetails loanInterestRecalculationDetails) { + + loanInterestRecalculationDetails.update(this.productInterestRecalculationDetails.getInterestRecalculationCompoundingMethod(), + this.productInterestRecalculationDetails.getRescheduleStrategyMethod()); + return loanInterestRecalculationDetails; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java new file mode 100644 index 000000000..8929a5201 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductInterestRecalculationDetails.java @@ -0,0 +1,93 @@ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +import org.mifosplatform.infrastructure.core.api.JsonCommand; +import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; +import org.springframework.data.jpa.domain.AbstractPersistable; + +/** + * Entity for capturing interest recalculation settings + * + * @author conflux + */ + +@Entity +@Table(name = "m_product_loan_recalculation_details") +public class LoanProductInterestRecalculationDetails extends AbstractPersistable { + + @OneToOne + @JoinColumn(name = "product_id", nullable = false) + private LoanProduct loanProduct; + + /** + * {@link InterestRecalculationCompoundingMethod} + */ + @Column(name = "compound_type_enum", nullable = false) + private Integer interestRecalculationCompoundingMethod; + + /** + * {@link LoanRescheduleStrategyMethod} + */ + @Column(name = "reschedule_strategy_enum", nullable = false) + private Integer rescheduleStrategyMethod; + + protected LoanProductInterestRecalculationDetails() { + // + } + + public static LoanProductInterestRecalculationDetails createFrom(final JsonCommand command) { + + final Integer interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt( + command.integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName)).getValue(); + + final Integer loanRescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt( + command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName)).getValue(); + + return new LoanProductInterestRecalculationDetails(interestRecalculationCompoundingMethod, loanRescheduleStrategyMethod); + } + + private LoanProductInterestRecalculationDetails(final Integer interestRecalculationCompoundingMethod, + final Integer rescheduleStrategyMethod) { + this.interestRecalculationCompoundingMethod = interestRecalculationCompoundingMethod; + this.rescheduleStrategyMethod = rescheduleStrategyMethod; + } + + public void updateProduct(final LoanProduct loanProduct) { + this.loanProduct = loanProduct; + } + + public Integer getInterestRecalculationCompoundingMethod() { + return this.interestRecalculationCompoundingMethod; + } + + public Integer getRescheduleStrategyMethod() { + return this.rescheduleStrategyMethod; + } + + public void update(final JsonCommand command, final Map actualChanges, final String localeAsInput) { + + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, + this.interestRecalculationCompoundingMethod)) { + final Integer newValue = command + .integerValueOfParameterNamed(LoanProductConstants.interestRecalculationCompoundingMethodParameterName); + actualChanges.put(LoanProductConstants.interestRecalculationCompoundingMethodParameterName, newValue); + actualChanges.put("locale", localeAsInput); + this.interestRecalculationCompoundingMethod = InterestRecalculationCompoundingMethod.fromInt(newValue).getValue(); + } + + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName, + this.rescheduleStrategyMethod)) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.rescheduleStrategyMethodParameterName); + actualChanges.put(LoanProductConstants.rescheduleStrategyMethodParameterName, newValue); + actualChanges.put("locale", localeAsInput); + this.rescheduleStrategyMethod = LoanRescheduleStrategyMethod.fromInt(newValue).getValue(); + } + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index 6213c14a7..68de2bf4e 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -100,18 +100,28 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche @Column(name = "grace_on_arrears_ageing", nullable = true) private Integer graceOnArrearsAgeing; + @Column(name = "days_in_month_enum", nullable = false) + private Integer daysInMonthType; + + @Column(name = "days_in_year_enum", nullable = false) + private Integer daysInYearType; + + @Column(name = "interest_recalculation_enabled") + private boolean isInterestRecalculationEnabled; + public static LoanProductRelatedDetail createFrom(final MonetaryCurrency currency, final BigDecimal principal, final BigDecimal nominalInterestRatePerPeriod, final PeriodFrequencyType interestRatePeriodFrequencyType, final BigDecimal nominalAnnualInterestRate, final InterestMethod interestMethod, final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repaymentEvery, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer numberOfRepayments, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, - final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing) { + 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, repaymentEvery, repaymentPeriodFrequencyType, numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, - inArrearsTolerance, graceOnArrearsAgeing); + inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } protected LoanProductRelatedDetail() { @@ -124,7 +134,8 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche final InterestCalculationPeriodMethod interestCalculationPeriodMethod, final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfRepayments, final Integer graceOnPrincipalPayment, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, - final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing) { + final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final Integer graceOnArrearsAgeing, + final Integer daysInMonthType, final Integer daysInYearType, final boolean isInterestRecalculationEnabled) { this.currency = currency; this.principal = defaultPrincipal; this.nominalInterestRatePerPeriod = defaultNominalInterestRatePerPeriod; @@ -145,6 +156,9 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.inArrearsTolerance = inArrearsTolerance; } this.graceOnArrearsAgeing = graceOnArrearsAgeing; + this.daysInMonthType = daysInMonthType; + this.daysInYearType = daysInYearType; + this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; } private Integer defaultToNullIfZero(final Integer value) { @@ -383,6 +397,28 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.graceOnArrearsAgeing = newValue; } + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInMonthTypeParameterName, this.daysInMonthType)) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInMonthTypeParameterName); + actualChanges.put(LoanProductConstants.daysInMonthTypeParameterName, newValue); + actualChanges.put("locale", localeAsInput); + this.daysInMonthType = newValue; + } + + if (command.isChangeInIntegerParameterNamed(LoanProductConstants.daysInYearTypeParameterName, this.daysInYearType)) { + final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.daysInYearTypeParameterName); + actualChanges.put(LoanProductConstants.daysInYearTypeParameterName, newValue); + actualChanges.put("locale", localeAsInput); + this.daysInYearType = newValue; + } + + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName, + this.isInterestRecalculationEnabled)) { + final boolean newValue = command + .booleanPrimitiveValueOfParameterNamed(LoanProductConstants.isInterestRecalculationEnabledParameterName); + actualChanges.put(LoanProductConstants.isInterestRecalculationEnabledParameterName, newValue); + this.isInterestRecalculationEnabled = newValue; + } + validateRepaymentPeriodWithGraceSettings(); return actualChanges; @@ -435,10 +471,18 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche } public DaysInMonthType fetchDaysInMonthType() { - return DaysInMonthType.ACTUAL; + return DaysInMonthType.fromInt(this.daysInMonthType); } public DaysInYearType fetchDaysInYearType() { - return DaysInYearType.ACTUAL; + return DaysInYearType.fromInt(this.daysInYearType); + } + + public boolean isInterestRecalculationEnabled() { + return this.isInterestRecalculationEnabled; + } + + public void updateIsInterestRecalculationEnabled(final boolean isInterestRecalculationEnabled) { + this.isInterestRecalculationEnabled = isInterestRecalculationEnabled; } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java new file mode 100644 index 000000000..d05826bb7 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanRescheduleStrategyMethod.java @@ -0,0 +1,50 @@ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.util.HashMap; +import java.util.Map; + +/*** + *
    + * People typically use either of the following settings when defining interest + * recalculation method: + *
  • RESCHEDULE_NEXT_REPAYMENTS
  • + *
  • REDUCE_NUMBER_OF_INSTALLMENTS
  • + *
  • REDUCE_EMI_AMOUNT
  • + *
+ */ + +public enum LoanRescheduleStrategyMethod { + + INVALID(0, "loanRescheduleStrategyMethod.invalid"), // + RESCHEDULE_NEXT_REPAYMENTS(1, "loanRescheduleStrategyMethod.reschedule.next.repayments"), // + REDUCE_NUMBER_OF_INSTALLMENTS(2, "loanRescheduleStrategyMethod.reduce.number.of.installments"), // + REDUCE_EMI_AMOUNT(3, "loanRescheduleStrategyMethod.reduce.emi.amount"); + + private final Integer value; + private final String code; + private static final Map intToEnumMap = new HashMap<>(); + + static { + for (final LoanRescheduleStrategyMethod type : LoanRescheduleStrategyMethod.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static LoanRescheduleStrategyMethod fromInt(final Integer ruleTypeValue) { + final LoanRescheduleStrategyMethod type = intToEnumMap.get(ruleTypeValue); + return type; + } + + private LoanRescheduleStrategyMethod(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java new file mode 100644 index 000000000..9312a1903 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/RecalculationFrequencyType.java @@ -0,0 +1,42 @@ +package org.mifosplatform.portfolio.loanproduct.domain; + +import java.util.HashMap; +import java.util.Map; + +public enum RecalculationFrequencyType { + INVALID(0, "interestRecalculationFrequencyType.invalid"), // + SAME_AS_REPAYMENT_PERIOD(1, "interestRecalculationFrequencyType.same.as.repayment.period"), // + DAILY(2, "interestRecalculationFrequencyType.daily"), // + WEEKLY(3, "interestRecalculationFrequencyType.weekly"), // + FORTNIGHTLY(4, "interestRecalculationFrequencyType.fortnightly"), // + MONTHLY(5, "interestRecalculationFrequencyType.monthly"); + + private final Integer value; + private final String code; + private static final Map intToEnumMap = new HashMap<>(); + + static { + for (final RecalculationFrequencyType type : RecalculationFrequencyType.values()) { + intToEnumMap.put(type.value, type); + } + } + + public static RecalculationFrequencyType fromInt(final Integer ruleTypeValue) { + if (ruleTypeValue == null) { return RecalculationFrequencyType.INVALID; } + final RecalculationFrequencyType type = intToEnumMap.get(ruleTypeValue); + return type; + } + + private RecalculationFrequencyType(final Integer value, final String code) { + this.value = value; + this.code = code; + } + + public Integer getValue() { + return this.value; + } + + public String getCode() { + return this.code; + } +} diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java index 5724417ab..21c0dc690 100755 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -61,7 +61,10 @@ public final class LoanProductDataValidator { LoanProductConstants.numberOfRepaymentVariationsForBorrowerCycleParameterName, LoanProductConstants.shortName, LoanProductConstants.multiDisburseLoanParameterName, LoanProductConstants.outstandingLoanBalanceParameterName, LoanProductConstants.maxTrancheCountParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, - LoanProductConstants.overdueDaysForNPAParameterName)); + LoanProductConstants.overdueDaysForNPAParameterName, LoanProductConstants.isInterestRecalculationEnabledParameterName, + LoanProductConstants.daysInYearTypeParameterName, LoanProductConstants.daysInMonthTypeParameterName, + LoanProductConstants.rescheduleStrategyMethodParameterName, + LoanProductConstants.interestRecalculationCompoundingMethodParameterName)); private final FromJsonHelper fromApiJsonHelper; @@ -264,6 +267,34 @@ public final class LoanProductDataValidator { baseDataValidator.reset().parameter(LoanProductConstants.overdueDaysForNPAParameterName).value(overdueDaysForNPA) .integerZeroOrGreater(); + /** + * { @link DaysInYearType } + */ + final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName, + element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull() + .isOneOfTheseValues(1, 360, 364, 365); + + /** + * { @link DaysInMonthType } + */ + final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName, + element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull() + .isOneOfTheseValues(1, 30); + + // Interest recalculation settings + final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed( + LoanProductConstants.isInterestRecalculationEnabledParameterName, element); + baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName) + .value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false); + + if (isInterestRecalculationEnabled != null) { + if (isInterestRecalculationEnabled.booleanValue()) { + validateInterestRecalculationParams(element, baseDataValidator); + } + } + // accounting related data validation final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 4); @@ -380,6 +411,26 @@ public final class LoanProductDataValidator { } } + private void validateInterestRecalculationParams(final JsonElement element, final DataValidatorBuilder baseDataValidator) { + + /** + * { @link InterestRecalculationCompoundingMethod } + */ + final Integer interestRecalculationCompoundingMethod = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.interestRecalculationCompoundingMethodParameterName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.interestRecalculationCompoundingMethodParameterName) + .value(interestRecalculationCompoundingMethod).notNull().inMinMaxRange(0, 3); + + /** + * { @link LoanRescheduleStrategyMethod } + */ + final Integer rescheduleStrategyMethod = this.fromApiJsonHelper.extractIntegerNamed( + LoanProductConstants.rescheduleStrategyMethodParameterName, element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.rescheduleStrategyMethodParameterName).value(rescheduleStrategyMethod) + .notNull().inMinMaxRange(1, 3); + + } + public void validateForUpdate(final String json, final LoanProduct loanProduct) { if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -574,6 +625,40 @@ public final class LoanProductDataValidator { .inMinMaxRange(0, 1); } + /** + * { @link DaysInYearType } + */ + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInYearTypeParameterName, element)) { + final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInYearTypeParameterName, + element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.daysInYearTypeParameterName).value(daysInYearType).notNull() + .isOneOfTheseValues(1, 360, 364, 365); + } + + /** + * { @link DaysInMonthType } + */ + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.daysInMonthTypeParameterName, element)) { + final Integer daysInMonthType = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.daysInMonthTypeParameterName, + element, Locale.getDefault()); + baseDataValidator.reset().parameter(LoanProductConstants.daysInMonthTypeParameterName).value(daysInMonthType).notNull() + .isOneOfTheseValues(1, 30); + } + + // Interest recalculation settings + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.isInterestRecalculationEnabledParameterName, element)) { + final Boolean isInterestRecalculationEnabled = this.fromApiJsonHelper.extractBooleanNamed( + LoanProductConstants.isInterestRecalculationEnabledParameterName, element); + baseDataValidator.reset().parameter(LoanProductConstants.isInterestRecalculationEnabledParameterName) + .value(isInterestRecalculationEnabled).notNull().isOneOfTheseValues(true, false); + + if (isInterestRecalculationEnabled != null) { + if (isInterestRecalculationEnabled.booleanValue()) { + validateInterestRecalculationParams(element, baseDataValidator); + } + } + } + final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault()); baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).ignoreIfNull().inMinMaxRange(1, 4); diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java index 94e5d0d02..2de842271 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformService.java @@ -29,4 +29,10 @@ public interface LoanDropdownReadPlatformService { List retrieveLoanCycleValueConditionTypeOptions(); + List retrieveInterestRecalculationCompoundingTypeOptions(); + + List retrieveRescheduleStrategyTypeOptions(); + + List retrieveInterestRecalculationFrequencyTypeOptions(); + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java index eed02f9c1..f9e929865 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanDropdownReadPlatformServiceImpl.java @@ -8,10 +8,13 @@ package org.mifosplatform.portfolio.loanproduct.service; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.amortizationType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestCalculationPeriodType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRateFrequencyType; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationCompoundingType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanCycleValueConditionType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.loanTermFrequencyType; import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.repaymentFrequencyType; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.rescheduleStrategyType; +import static org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations.interestRecalculationFrequencyType; import java.util.ArrayList; import java.util.Arrays; @@ -25,8 +28,11 @@ import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrateg import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; +import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -112,4 +118,37 @@ public class LoanDropdownReadPlatformServiceImpl implements LoanDropdownReadPlat return repaymentFrequencyOptions; } + @Override + public List retrieveInterestRecalculationCompoundingTypeOptions() { + + final List interestRecalculationCompoundingTypeOptions = Arrays.asList( + interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.NONE), + interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.FEE), + interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST), + interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE)); + return interestRecalculationCompoundingTypeOptions; + } + + @Override + public List retrieveRescheduleStrategyTypeOptions() { + + final List rescheduleStrategyTypeOptions = Arrays.asList( + rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT), + rescheduleStrategyType(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS), + rescheduleStrategyType(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS)); + return rescheduleStrategyTypeOptions; + } + + @Override + public List retrieveInterestRecalculationFrequencyTypeOptions() { + + final List interestRecalculationFrequencyTypeOptions = Arrays.asList( + interestRecalculationFrequencyType(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD), + interestRecalculationFrequencyType(RecalculationFrequencyType.DAILY), + interestRecalculationFrequencyType(RecalculationFrequencyType.WEEKLY), + interestRecalculationFrequencyType(RecalculationFrequencyType.FORTNIGHTLY), + interestRecalculationFrequencyType(RecalculationFrequencyType.MONTHLY)); + return interestRecalculationFrequencyTypeOptions; + } + } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java index 025b2fa61..d0f8b0460 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanEnumerations.java @@ -12,13 +12,16 @@ import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanaccount.data.LoanStatusEnumData; import org.mifosplatform.portfolio.loanaccount.data.LoanTransactionEnumData; import org.mifosplatform.portfolio.loanaccount.domain.LoanStatus; -import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanaccount.domain.LoanTermVariationType; +import org.mifosplatform.portfolio.loanaccount.domain.LoanTransactionType; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestCalculationPeriodMethod; import org.mifosplatform.portfolio.loanproduct.domain.InterestMethod; +import org.mifosplatform.portfolio.loanproduct.domain.InterestRecalculationCompoundingMethod; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType; +import org.mifosplatform.portfolio.loanproduct.domain.LoanRescheduleStrategyMethod; +import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; public class LoanEnumerations { @@ -32,6 +35,8 @@ public class LoanEnumerations { public static final String PAYMENT_TYPE = "paymentType"; public static final String ACCOUNTING_RULE_TYPE = "accountingRule"; public static final String LOAN_TYPE = "loanType"; + public static final String INTEREST_RECALCULATION_COMPOUNDING_TYPE = "interestRecalculationCompoundingType"; + public static final String RESCHEDULE_STRATEGY_TYPE = "rescheduleStrategyType"; public static EnumOptionData loanEnumueration(final String typeName, final int id) { if (typeName.equals(LOAN_TERM_FREQUENCY_TYPE)) { @@ -50,8 +55,12 @@ public class LoanEnumerations { return interestCalculationPeriodType(id); } else if (typeName.equals(ACCOUNTING_RULE_TYPE)) { return AccountingEnumerations.accountingRuleType(id); - } else if (typeName.equals(LOAN_TYPE)) { - return AccountEnumerations.loanType(id); + } else if (typeName.equals(LOAN_TYPE)) { + return AccountEnumerations.loanType(id); + } else if (typeName.equals(INTEREST_RECALCULATION_COMPOUNDING_TYPE)) { + return interestRecalculationCompoundingType(id); + } else if (typeName.equals(RESCHEDULE_STRATEGY_TYPE)) { + return rescheduleStrategyType(id); } return null; } @@ -452,4 +461,93 @@ public class LoanEnumerations { return optionData; } + public static EnumOptionData interestRecalculationCompoundingType(final int id) { + return interestRecalculationCompoundingType(InterestRecalculationCompoundingMethod.fromInt(id)); + } + + public static EnumOptionData interestRecalculationCompoundingType(final InterestRecalculationCompoundingMethod type) { + EnumOptionData optionData = null; + switch (type) { + case FEE: + optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.FEE.getValue().longValue(), + InterestRecalculationCompoundingMethod.FEE.getCode(), "Fee"); + break; + case INTEREST: + optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST.getValue().longValue(), + InterestRecalculationCompoundingMethod.INTEREST.getCode(), "Interest"); + break; + case INTEREST_AND_FEE: + optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getValue().longValue(), + InterestRecalculationCompoundingMethod.INTEREST_AND_FEE.getCode(), "Fee and Interest"); + break; + default: + optionData = new EnumOptionData(InterestRecalculationCompoundingMethod.NONE.getValue().longValue(), + InterestRecalculationCompoundingMethod.NONE.getCode(), "None"); + break; + } + return optionData; + } + + public static EnumOptionData rescheduleStrategyType(final int id) { + return rescheduleStrategyType(LoanRescheduleStrategyMethod.fromInt(id)); + } + + public static EnumOptionData rescheduleStrategyType(final LoanRescheduleStrategyMethod type) { + EnumOptionData optionData = null; + switch (type) { + case REDUCE_EMI_AMOUNT: + optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getValue().longValue(), + LoanRescheduleStrategyMethod.REDUCE_EMI_AMOUNT.getCode(), "Reduce EMI amount"); + break; + case REDUCE_NUMBER_OF_INSTALLMENTS: + optionData = new EnumOptionData(LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getValue().longValue(), + LoanRescheduleStrategyMethod.REDUCE_NUMBER_OF_INSTALLMENTS.getCode(), "Reduce number of installments"); + break; + case RESCHEDULE_NEXT_REPAYMENTS: + optionData = new EnumOptionData(LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getValue().longValue(), + LoanRescheduleStrategyMethod.RESCHEDULE_NEXT_REPAYMENTS.getCode(), "Reschedule next repayments"); + break; + default: + optionData = new EnumOptionData(LoanRescheduleStrategyMethod.INVALID.getValue().longValue(), + LoanRescheduleStrategyMethod.INVALID.getCode(), "Invalid"); + break; + } + return optionData; + } + + public static EnumOptionData interestRecalculationFrequencyType(final int id) { + return interestRecalculationFrequencyType(RecalculationFrequencyType.fromInt(id)); + } + + public static EnumOptionData interestRecalculationFrequencyType(final RecalculationFrequencyType type) { + EnumOptionData optionData = null; + switch (type) { + case DAILY: + optionData = new EnumOptionData(RecalculationFrequencyType.DAILY.getValue().longValue(), + RecalculationFrequencyType.DAILY.getCode(), "Daily"); + break; + case FORTNIGHTLY: + optionData = new EnumOptionData(RecalculationFrequencyType.FORTNIGHTLY.getValue().longValue(), + RecalculationFrequencyType.FORTNIGHTLY.getCode(), "Fortnightly"); + break; + case MONTHLY: + optionData = new EnumOptionData(RecalculationFrequencyType.MONTHLY.getValue().longValue(), + RecalculationFrequencyType.MONTHLY.getCode(), "Monthly"); + break; + case SAME_AS_REPAYMENT_PERIOD: + optionData = new EnumOptionData(RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getValue().longValue(), + RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD.getCode(), "Same as repayment period"); + break; + case WEEKLY: + optionData = new EnumOptionData(RecalculationFrequencyType.WEEKLY.getValue().longValue(), + RecalculationFrequencyType.WEEKLY.getCode(), "Weekly"); + break; + default: + optionData = new EnumOptionData(RecalculationFrequencyType.INVALID.getValue().longValue(), + RecalculationFrequencyType.INVALID.getCode(), "Invalid"); + break; + + } + return optionData; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index 2add0d0f0..4c71b2666 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -21,8 +21,10 @@ import org.mifosplatform.infrastructure.security.service.PlatformSecurityContext import org.mifosplatform.organisation.monetary.data.CurrencyData; import org.mifosplatform.portfolio.charge.data.ChargeData; import org.mifosplatform.portfolio.charge.service.ChargeReadPlatformService; +import org.mifosplatform.portfolio.common.service.CommonEnumerations; import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; +import org.mifosplatform.portfolio.loanproduct.data.LoanProductInterestRecalculationData; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; @@ -121,9 +123,12 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "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, " + "lp.allow_multiple_disbursals as multiDisburseLoan, lp.max_disbursals as maxTrancheCount, lp.max_outstanding_loan_balance as outstandingLoanBalance, " + + "lp.days_in_month_enum as daysInMonth, lp.days_in_year_enum as daysInYear, lp.interest_recalculation_enabled as isInterestRecalculationEnabled, " + + "lpr.id as lprId, lpr.product_id as productId, lpr.compound_type_enum as compoundType, lpr.reschedule_strategy_enum as rescheduleStrategy, " + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId " + " from m_product_loan lp " - + " left join m_fund f on f.id = lp.fund_id" + + " left join m_fund f on f.id = lp.fund_id " + + " left join m_product_loan_recalculation_details lpr on lpr.product_id=lp.id " + " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id" + " join m_currency curr on curr.code = lp.currency_code"; } @@ -131,7 +136,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo @Override public LoanProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - final Long id = rs.getLong("id"); + final Long id = JdbcSupport.getLong(rs, "id"); final String name = rs.getString("name"); final String shortName = rs.getString("shortName"); final String description = rs.getString("description"); @@ -221,6 +226,27 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final Integer maxTrancheCount = rs.getInt("maxTrancheCount"); final BigDecimal outstandingLoanBalance = rs.getBigDecimal("outstandingLoanBalance"); + final int daysInMonth = JdbcSupport.getInteger(rs, "daysInMonth"); + final EnumOptionData daysInMonthType = CommonEnumerations.daysInMonthType(daysInMonth); + final int daysInYear = JdbcSupport.getInteger(rs, "daysInYear"); + final EnumOptionData daysInYearType = CommonEnumerations.daysInYearType(daysInYear); + final boolean isInterestRecalculationEnabled = rs.getBoolean("isInterestRecalculationEnabled"); + + LoanProductInterestRecalculationData interestRecalculationData = null; + if (isInterestRecalculationEnabled) { + + final Long lprId = JdbcSupport.getLong(rs, "lprId"); + final Long productId = JdbcSupport.getLong(rs, "productId"); + final int compoundTypeEnumValue = JdbcSupport.getInteger(rs, "compoundType"); + final EnumOptionData interestRecalculationCompoundingType = LoanEnumerations + .interestRecalculationCompoundingType(compoundTypeEnumValue); + final int rescheduleStrategyEnumValue = JdbcSupport.getInteger(rs, "rescheduleStrategy"); + final EnumOptionData rescheduleStrategyType = LoanEnumerations.rescheduleStrategyType(rescheduleStrategyEnumValue); + + interestRecalculationData = new LoanProductInterestRecalculationData(lprId, productId, + interestRecalculationCompoundingType, rescheduleStrategyType); + } + return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, @@ -229,7 +255,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo graceOnInterestCharged, this.charges, accountingRuleType, includeInBorrowerCycle, useBorrowerCycle, startDate, closeDate, status, externalId, principalVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, numberOfRepaymentVariationsForBorrowerCycle, multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, - graceOnArrearsAgeing, overdueDaysForNPA); + graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, + interestRecalculationData); } } diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index 7adfaaa6a..c8bbd81d1 100644 --- a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -92,6 +92,9 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command, this.aprCalculator); + if (loanproduct.isInterestRecalculationEnabled()) { + loanproduct.updateLoanProductForInterestRecalculationDetails(); + } this.loanProductRepository.save(loanproduct); diff --git a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java index 6f2bd757c..0301de18c 100644 --- a/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java +++ b/mifosng-provider/src/test/java/org/mifosplatform/portfolio/loanaccount/LoanProductRelatedDetailTestHelper.java @@ -3,6 +3,8 @@ package org.mifosplatform.portfolio.loanaccount; import java.math.BigDecimal; import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency; +import org.mifosplatform.portfolio.common.domain.DaysInMonthType; +import org.mifosplatform.portfolio.common.domain.DaysInYearType; import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator; import org.mifosplatform.portfolio.loanproduct.domain.AmortizationMethod; @@ -128,9 +130,13 @@ public class LoanProductRelatedDetailTestHelper { final Integer graceOnInterestCharged = Integer.valueOf(0); final Integer graceOnArrearsAgeing = Integer.valueOf(0); + final Integer daysInMonthType = DaysInMonthType.ACTUAL.getValue(); + final Integer daysInYearType = DaysInYearType.ACTUAL.getValue(); + final boolean isInterestRecalculationEnabled = false; + return new LoanProductRelatedDetail(currency, defaultPrincipal, defaultNominalInterestRatePerPeriod, interestPeriodFrequencyType, defaultAnnualNominalInterestRate, interestMethod, interestCalculationPeriodMethod, repayEvery, repaymentFrequencyType, defaultNumberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, amortizationMethod, - inArrearsTolerance, graceOnArrearsAgeing); + inArrearsTolerance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled); } } \ No newline at end of file