diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index d7e1956e8..cc9577fd5 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -11676,6 +11676,12 @@ An enumeration that indicates the type of transaction processing strategy to be If Specified as true, arrears will be identified based on original schedule.
+ + allowAttributeOverrides + + + Specifies if select attributes may be overridden for individual loan accounts.
+ @@ -12942,7 +12948,7 @@ GET https://DomainName/api/v1/loanproducts/{productId} numberOfRepaymentVariationsForBorrowerCycle, interestRateVariationsForBorrowerCycle, multiDisburseLoan,maxTrancheCount, - outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf + outstandingLoanBalance,overdueDaysForNPA,holdGuaranteeFunds, principalThresholdForLastInstalment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, allowAttributeOverrides @@ -13045,6 +13051,16 @@ Request Body: "maxValue":"20" } ], +"allowAttributeOverrides": + {amortizationType : true, + interestType : true, + transactionProcessingStrategyId : false, + interestCalculationPeriodType : true, + inArrearsTolerance : false, + repaymentEvery : true, + graceOnPrincipalAndInterestPayment : true, + graceOnArrearsAgeing : true}, + "accountingRule":"2", "name":"product 5", "shortName":"prd5", diff --git a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java index 7e147ed64..37f313263 100644 --- a/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java +++ b/mifosng-provider/src/integrationTest/java/org/mifosplatform/integrationtests/ClientLoanIntegrationTest.java @@ -40,6 +40,7 @@ import org.mifosplatform.integrationtests.common.savings.SavingsAccountHelper; import org.mifosplatform.integrationtests.common.savings.SavingsProductHelper; import org.mifosplatform.integrationtests.common.savings.SavingsStatusChecker; +import com.google.gson.JsonObject; import com.jayway.restassured.builder.RequestSpecBuilder; import com.jayway.restassured.builder.ResponseSpecBuilder; import com.jayway.restassured.http.ContentType; @@ -4239,6 +4240,7 @@ public class ClientLoanIntegrationTest { } } + /*** * Test case to verify Mifos Style payment strategy */ @@ -4772,5 +4774,124 @@ public class ClientLoanIntegrationTest { .withMinimumOpenningBalance(minOpenningBalance).build(); return SavingsProductHelper.createSavingsProduct(savingsProductJSON, requestSpec, responseSpec); } + + @Test + public void testLoanProductConfiguration() { + final String proposedAmount = "5000"; + JsonObject loanProductConfigurationAsTrue = new JsonObject(); + loanProductConfigurationAsTrue = this.createLoanProductConfigurationDetail(loanProductConfigurationAsTrue, true); + JsonObject loanProductConfigurationAsFalse = new JsonObject(); + loanProductConfigurationAsFalse = this.createLoanProductConfigurationDetail(loanProductConfigurationAsFalse, false); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012"); + Integer loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder() + .withAmortizationTypeAsEqualInstallments().withRepaymentTypeAsMonth().withRepaymentAfterEvery("1") + .withRepaymentStrategy(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY).withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10").withMoratorium("2", "3") + .withLoanProductConfiguration(loanProductConfigurationAsTrue).build(null)); + System.out.println("-----------------------LOAN PRODUCT CREATED WITH ATTRIBUTE CONFIGURATION AS TRUE--------------------------" + + loanProductID); + Integer loanID = applyForLoanApplicationWithProductConfigurationAsTrue(clientID, loanProductID, proposedAmount); + System.out.println("------------------------LOAN CREATED WITH ID------------------------------" + loanID); + + loanProductID = this.loanTransactionHelper.getLoanProductId(new LoanProductTestBuilder().withAmortizationTypeAsEqualInstallments() + .withRepaymentTypeAsMonth().withRepaymentAfterEvery("1") + .withRepaymentStrategy(LoanProductTestBuilder.MIFOS_STANDARD_STRATEGY).withInterestTypeAsDecliningBalance() + .withInterestCalculationPeriodTypeAsDays().withInArrearsTolerance("10").withMoratorium("2", "3") + .withLoanProductConfiguration(loanProductConfigurationAsFalse).build(null)); + System.out.println("-------------------LOAN PRODUCT CREATED WITH ATTRIBUTE CONFIGURATION AS FALSE----------------------" + + loanProductID); + /* + * Try to override attribute values in loan account when attribute + * configurations are set to false at product level + */ + loanID = applyForLoanApplicationWithProductConfigurationAsFalse(clientID, loanProductID, proposedAmount); + System.out.println("--------------------------LOAN CREATED WITH ID-------------------------" + loanID); + this.validateIfValuesAreNotOverridden(loanID, loanProductID); + } + + private void validateIfValuesAreNotOverridden(Integer loanID, Integer loanProductID) { + assertEquals( + this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, "amortizationType"), + this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "amortizationType")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, "interestType"), + this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "interestType")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, + "transactionProcessingStrategyId"), this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "transactionProcessingStrategyId")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, + "interestCalculationPeriodType"), this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "interestCalculationPeriodType")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, + "repaymentFrequencyType"), this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "repaymentFrequencyType")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, + "graceOnPrincipalPayment"), this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "graceOnPrincipalPayment")); + assertEquals(this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, + "graceOnInterestPayment"), this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, + "graceOnInterestPayment")); + assertEquals( + this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, "inArrearsTolerance"), + this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "inArrearsTolerance")); + assertEquals( + this.loanTransactionHelper.getLoanProductDetail(this.requestSpec, this.responseSpec, loanProductID, "graceOnArrearsAgeing"), + this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, loanID, "graceOnArrearsAgeing")); + } + + private JsonObject createLoanProductConfigurationDetail(JsonObject loanProductConfiguration, Boolean bool) { + loanProductConfiguration.addProperty("amortizationType", bool); + loanProductConfiguration.addProperty("interestType", bool); + loanProductConfiguration.addProperty("transactionProcessingStrategyId", bool); + loanProductConfiguration.addProperty("interestCalculationPeriodType", bool); + loanProductConfiguration.addProperty("inArrearsTolerance", bool); + loanProductConfiguration.addProperty("repaymentEvery", bool); + loanProductConfiguration.addProperty("graceOnPrincipalAndInterestPayment", bool); + loanProductConfiguration.addProperty("graceOnArrearsAgeing", bool); + return loanProductConfiguration; + } + + private Integer applyForLoanApplicationWithProductConfigurationAsTrue(final Integer clientID, final Integer loanProductID, + String principal) { + System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() // + .withPrincipal(principal) // + .withRepaymentEveryAfter("1") // + .withLoanTermFrequency("4") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("4") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("2") // + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // + .withExpectedDisbursementDate("1 March 2014") // + .withSubmittedOnDate("1 March 2014") // + .build(clientID.toString(), loanProductID.toString(), null); + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } + + private Integer applyForLoanApplicationWithProductConfigurationAsFalse(final Integer clientID, final Integer loanProductID, + String principal) { + System.out.println("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------"); + final String loanApplicationJSON = new LoanApplicationTestBuilder() + // + .withPrincipal(principal) + // + .withRepaymentEveryAfter("2") + // + .withAmortizationTypeAsEqualPrincipalPayments().withRepaymentFrequencyTypeAsWeeks() + .withwithRepaymentStrategy(LoanProductTestBuilder.RBI_INDIA_STRATEGY).withInterestTypeAsFlatBalance() + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withPrincipalGrace("1").withInterestGrace("1") + .withLoanTermFrequency("4") // + .withLoanTermFrequencyAsMonths() // + .withNumberOfRepayments("4") // + .withRepaymentFrequencyTypeAsMonths() // + .withInterestRatePerPeriod("2") // + .withInterestCalculationPeriodTypeSameAsRepaymentPeriod() // + .withExpectedDisbursementDate("1 March 2014") // + .withSubmittedOnDate("1 March 2014") // + .build(clientID.toString(), loanProductID.toString(), null); + + return this.loanTransactionHelper.getLoanId(loanApplicationJSON); + } } \ No newline at end of file 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 09e1259c1..c46dfa3ef 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 @@ -74,8 +74,9 @@ public class LoanApplicationTestBuilder { map.put("expectedDisbursementDate", this.expectedDisbursmentDate); map.put("submittedOnDate", this.submittedOnDate); map.put("loanType", this.loanType); - map.put("repaymentsStartingFromDate", this.repaymentsStartingFromDate); - + if(repaymentsStartingFromDate != null){ + map.put("repaymentsStartingFromDate", this.repaymentsStartingFromDate); + } 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 8b34d29f1..d5352b194 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 @@ -14,6 +14,7 @@ import org.mifosplatform.integrationtests.common.Utils; import org.mifosplatform.integrationtests.common.accounting.Account; import com.google.gson.Gson; +import com.google.gson.JsonObject; public class LoanProductTestBuilder { @@ -97,7 +98,10 @@ public class LoanProductTestBuilder { private String minimumGuaranteeFromOwnFunds = null; private String minimumGuaranteeFromGuarantor = null; private String isArrearsBasedOnOriginalSchedule = null; - + private String graceOnPrincipalPayment = "1"; + private String graceOnInterestPayment = "1"; + private JsonObject allowAttributeOverrides = null; + public String build(final String chargeId) { final HashMap map = new HashMap<>(); @@ -166,6 +170,11 @@ public class LoanProductTestBuilder { map.put("minimumGuaranteeFromOwnFunds", this.minimumGuaranteeFromOwnFunds); } } + map.put("graceOnPrincipalPayment", graceOnPrincipalPayment); + map.put("graceOnInterestPayment", graceOnInterestPayment); + if(allowAttributeOverrides != null){ + map.put("allowAttributeOverrides", this.allowAttributeOverrides); + } return new Gson().toJson(map); } @@ -422,4 +431,14 @@ public class LoanProductTestBuilder { return this; } + public LoanProductTestBuilder withMoratorium(String principal, String interest) { + this.graceOnPrincipalPayment = principal; + this.graceOnInterestPayment = interest; + return this; + } + + public LoanProductTestBuilder withLoanProductConfiguration(JsonObject loanProductConfigurableAttributes) { + this.allowAttributeOverrides = loanProductConfigurableAttributes; + return this; + } } 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 b5737d282..2b3dbe8d5 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 @@ -452,6 +452,7 @@ public class LoansApiResource { } Collection productOptions = null; + LoanProductData product = null; Collection loanTermFrequencyTypeOptions = null; Collection repaymentFrequencyTypeOptions = null; Collection repaymentStrategyOptions = null; @@ -472,6 +473,8 @@ public class LoansApiResource { final boolean template = ApiParameterHelper.template(uriInfo.getQueryParameters()); if (template) { productOptions = this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(); + product = this.loanProductReadPlatformService.retrieveLoanProduct(loanBasicDetails.loanProductId()); + loanBasicDetails.setProduct(product); loanTermFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveLoanTermFrequencyTypeOptions(); repaymentFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveRepaymentFrequencyTypeOptions(); interestRateFrequencyTypeOptions = this.dropdownReadPlatformService.retrieveInterestRateFrequencyTypeOptions(); 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 bbc6ad1c0..e29612ce1 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 @@ -35,6 +35,7 @@ import org.mifosplatform.portfolio.loanaccount.loanschedule.data.LoanScheduleDat import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVariationData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.TransactionProcessingStrategyData; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionType; import org.mifosplatform.portfolio.note.data.NoteData; import org.springframework.util.CollectionUtils; @@ -162,7 +163,7 @@ public class LoanAccountData { private final Collection emiAmountVariations; - private final LoanProductData product; + private LoanProductData product; private final Map memberVariations; @@ -300,6 +301,7 @@ public class LoanAccountData { emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance); + } /** @@ -420,6 +422,7 @@ public class LoanAccountData { emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance); + } public static LoanAccountData populateClientDefaults(final LoanAccountData acc, final LoanAccountData clientAcc) { @@ -566,6 +569,7 @@ public class LoanAccountData { memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance); + } public static LoanAccountData populateGroupDefaults(final LoanAccountData acc, final LoanAccountData groupAcc) { @@ -591,6 +595,7 @@ public class LoanAccountData { acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + } public static LoanAccountData loanProductWithTemplateDefaults(final LoanProductData product, @@ -783,6 +788,7 @@ public class LoanAccountData { acc.memberVariations, product, acc.inArrears, product.getGraceOnArrearsAgeing(), product.overdueFeeCharges(), acc.isNPA, product.getDaysInMonthType(), product.getDaysInYearType(), product.isInterestRecalculationEnabled(), product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance); + } /* @@ -883,6 +889,10 @@ public class LoanAccountData { final PortfolioAccountData linkedAccount, final Collection disbursementDetails, final Collection emiAmountVariations, final Collection overdueCharges, final PaidInAdvanceData paidInAdvance) { + LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; + if (acc.product != null) { + loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); + } 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, @@ -1001,6 +1011,7 @@ public class LoanAccountData { acc.graceOnArrearsAgeing, acc.overdueCharges, acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance); + } public static LoanAccountData withInterestRecalculationCalendarData(final LoanAccountData acc, final CalendarData calendarData) { @@ -1114,6 +1125,7 @@ public class LoanAccountData { final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, final LoanInterestRecalculationData interestRecalculationData, final LoanScheduleData originalSchedule, final Boolean createStandingInstructionAtDisbursement, final PaidInAdvanceData paidInAdvance) { + this.id = id; this.accountNo = accountNo; this.status = status; @@ -1273,6 +1285,9 @@ public class LoanAccountData { this.originalSchedule = originalSchedule; this.createStandingInstructionAtDisbursement = createStandingInstructionAtDisbursement; this.paidInAdvance = paidInAdvance; + if(this.product != null){ + this.product.setloanProductConfigurableAttributes(product.getloanProductConfigurableAttributes()); + } } public RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData() { @@ -1342,6 +1357,10 @@ public class LoanAccountData { return this.product; } + public void setProduct(LoanProductData product){ + this.product = product; + } + public GroupGeneralData groupData() { return this.group; } 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 3f1b35cd0..b9e85e65d 100755 --- 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 @@ -232,6 +232,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa validateSubmittedOnDate(newLoanApplication); final LoanProductRelatedDetail productRelatedDetail = newLoanApplication.repaymentScheduleDetail(); + + if(loanProduct.getLoanProductConfigurableAttributes() != null){ + updateProductRelatedDetails(productRelatedDetail,newLoanApplication); + } + this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(newLoanApplication.getTermFrequency(), newLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(), @@ -301,6 +306,43 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } } + private void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, + Loan loan){ + final Boolean amortization = loan.loanProduct().getLoanProductConfigurableAttributes().getAmortizationBoolean(); + final Boolean arrearsTolerance = loan.loanProduct().getLoanProductConfigurableAttributes().getArrearsToleranceBoolean(); + final Boolean graceOnArrearsAging = loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnArrearsAgingBoolean(); + final Boolean interestCalcPeriod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestCalcPeriodBoolean(); + final Boolean interestMethod = loan.loanProduct().getLoanProductConfigurableAttributes().getInterestMethodBoolean(); + final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanProductConfigurableAttributes().getGraceOnPrincipalAndInterestPaymentBoolean(); + final Boolean repaymentEvery = loan.loanProduct().getLoanProductConfigurableAttributes().getRepaymentEveryBoolean(); + final Boolean transactionProcessingStrategy = loan.loanProduct().getLoanProductConfigurableAttributes().getTransactionProcessingStrategyBoolean(); + + if(!amortization){ + productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod()); + } + if(!arrearsTolerance){ + productRelatedDetail.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getArrearsTolerance()); + } + if(!graceOnArrearsAging){ + productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing()); + } + if(!interestCalcPeriod){ + productRelatedDetail.setInterestCalculationPeriodMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestCalculationPeriodMethod()); + } + if(!interestMethod){ + productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod()); + } + if(!graceOnPrincipalAndInterestPayment){ + productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment()); + productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment()); + } + if(!repaymentEvery){ + productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery()); + } + if(!transactionProcessingStrategy){ + loan.updateTransactionProcessingStrategy(loan.loanProduct().getRepaymentStrategy()); + } + } private void createAndPersistCalendarInstanceForInterestRecalculation(final Loan loan) { LocalDate calendarStartDate = loan.loanInterestRecalculationDetails().getRestFrequencyLocalDate(); @@ -461,10 +503,10 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa } final String productIdParamName = "productId"; - if (changes.containsKey(productIdParamName)) { - final Long productId = command.longValueOfParameterNamed(productIdParamName); - final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); - if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } + if (changes.containsKey(productIdParamName)) { + final Long productId = command.longValueOfParameterNamed(productIdParamName); + final LoanProduct loanProduct = this.loanProductRepository.findOne(productId); + if (loanProduct == null) { throw new LoanProductNotFoundException(productId); } existingLoanApplication.updateLoanProduct(loanProduct); if (!changes.containsKey("interestRateFrequencyType")) { @@ -494,6 +536,11 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa existingLoanApplication.updateIsInterestRecalculationEnabled(); validateSubmittedOnDate(existingLoanApplication); + final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail(); + if(existingLoanApplication.loanProduct().getLoanProductConfigurableAttributes() != null){ + updateProductRelatedDetails(productRelatedDetail,existingLoanApplication); + } + final String fundIdParamName = "fundId"; if (changes.containsKey(fundIdParamName)) { final Long fundId = command.longValueOfParameterNamed(fundIdParamName); @@ -546,7 +593,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa existingLoanApplication.updateLoanCharges(possiblyModifedLoanCharges); } - final LoanProductRelatedDetail productRelatedDetail = existingLoanApplication.repaymentScheduleDetail(); this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(), existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(), 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 09d6f7c00..8e613f28d 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 @@ -89,5 +89,15 @@ public interface LoanProductConstants { // Fixed installment configuration related public static final String canDefineEmiAmountParamName = "canDefineInstallmentAmount"; public static final String installmentAmountInMultiplesOfParamName = "installmentAmountInMultiplesOf"; + + //Loan Configurable Attributes + public static final String allowAttributeOverridesParamName = "allowAttributeOverrides"; + public static final String amortizationTypeParamName = "amortizationType"; + public static final String interestTypeParamName = "interestType"; + public static final String transactionProcessingStrategyIdParamName = "transactionProcessingStrategyId"; + public static final String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType"; + public static final String inArrearsToleranceParamName = "inArrearsTolerance"; + public static final String repaymentEveryParamName = "repaymentEvery"; + public static final String graceOnPrincipalAndInterestPaymentParamName = "graceOnPrincipalAndInterestPayment"; } 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 8d5e19047..7c4cc0c3b 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 @@ -30,6 +30,7 @@ import org.mifosplatform.portfolio.loanaccount.data.LoanInterestRecalculationDat 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.LoanProductConfigurableAttributes; import org.mifosplatform.portfolio.loanproduct.service.LoanEnumerations; import org.mifosplatform.portfolio.paymenttype.data.PaymentTypeData; import org.springframework.util.CollectionUtils; @@ -143,6 +144,7 @@ public class LoanProductData { private final Boolean holdGuaranteeFunds; private final LoanProductGuaranteeData productGuaranteeData; private final Boolean accountMovesOutOfNPAOnlyOnArrearsCompletion; + private LoanProductConfigurableAttributes allowAttributeOverrides; /** * Used when returning lookup information about loan product for dropdowns. @@ -203,6 +205,7 @@ public class LoanProductData { final Integer minimumDaysBetweenDisbursalAndFirstRepayment = null; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; + final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -214,7 +217,8 @@ public class LoanProductData { graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf); + installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + } public static LoanProductData lookupWithCurrency(final Long id, final String name, final CurrencyData currency) { @@ -274,6 +278,7 @@ public class LoanProductData { final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; + final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -285,7 +290,8 @@ public class LoanProductData { graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf); + installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + } public static LoanProductData sensibleDefaultsForNewLoanProductCreation() { @@ -352,6 +358,7 @@ public class LoanProductData { final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = false; final boolean canDefineInstallmentAmount = false; final Integer installmentAmountInMultiplesOf = null; + final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -363,7 +370,8 @@ public class LoanProductData { multiDisburseLoan, maxTrancheCount, outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, productGuaranteeData, principalThresholdForLastInstallment, - accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf); + accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, installmentAmountInMultiplesOf, loanProductConfigurableAttributes); + } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map accountingMappings, @@ -397,7 +405,7 @@ public class LoanProductData { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds, final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount, - Integer installmentAmountInMultiplesOf) { + Integer installmentAmountInMultiplesOf, LoanProductConfigurableAttributes allowAttributeOverrides) { this.id = id; this.name = name; this.shortName = shortName; @@ -472,6 +480,7 @@ public class LoanProductData { this.productGuaranteeData = loanProductGuaranteeData; this.principalThresholdForLastInstallment = principalThresholdForLastInstallment; this.accountMovesOutOfNPAOnlyOnArrearsCompletion = accountMovesOutOfNPAOnlyOnArrearsCompletion; + this.allowAttributeOverrides = allowAttributeOverrides; this.daysInMonthTypeOptions = null; this.daysInYearTypeOptions = null; @@ -587,6 +596,7 @@ public class LoanProductData { this.daysInYearTypeOptions = daysInYearTypeOptions; this.interestRecalculationCompoundingTypeOptions = interestRecalculationCompoundingTypeOptions; this.rescheduleStrategyTypeOptions = rescheduleStrategyTypeOptions; + this.allowAttributeOverrides = productData.allowAttributeOverrides; if (CollectionUtils.isEmpty(interestRecalculationFrequencyTypeOptions)) { this.interestRecalculationFrequencyTypeOptions = null; @@ -850,4 +860,12 @@ public class LoanProductData { public boolean canDefineInstallmentAmount() { return this.canDefineInstallmentAmount; } + + public LoanProductConfigurableAttributes getloanProductConfigurableAttributes() { + return this.allowAttributeOverrides; + } + + public void setloanProductConfigurableAttributes(LoanProductConfigurableAttributes loanProductConfigurableAttributes) { + this.allowAttributeOverrides = loanProductConfigurableAttributes; + } } \ No newline at end of file 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 f00e55940..0893ce5f3 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 @@ -46,6 +46,7 @@ import org.mifosplatform.portfolio.fund.domain.Fund; import org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.mifosplatform.portfolio.loanproduct.LoanProductConstants; import org.springframework.data.jpa.domain.AbstractPersistable; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -136,6 +137,9 @@ public class LoanProduct extends AbstractPersistable { @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true) private LoanProductGuaranteeDetails loanProductGuaranteeDetails; + @OneToOne(cascade = CascadeType.ALL, mappedBy = "loanProduct", optional = true, orphanRemoval = true) + private LoanProductConfigurableAttributes loanConfigurableAttributes; + @Column(name = "principal_threshold_for_last_installment", scale = 2, precision = 5, nullable = false) private BigDecimal principalThresholdForLastInstallment; @@ -237,6 +241,13 @@ public class LoanProduct extends AbstractPersistable { loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command); } + LoanProductConfigurableAttributes loanConfigurableAttributes = null; + if (command.parameterExists(LoanProductConstants.allowAttributeOverridesParamName)) { + loanConfigurableAttributes = LoanProductConfigurableAttributes.createFrom(command); + } else { + loanConfigurableAttributes = LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes(); + } + BigDecimal principalThresholdForLastInstallment = command .bigDecimalValueOfParameterNamed(LoanProductConstants.principalThresholdForLastInstallmentParamName); @@ -259,7 +270,7 @@ public class LoanProduct extends AbstractPersistable { outstandingLoanBalance, graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationSettings, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuarantorFunds, loanProductGuaranteeDetails, principalThresholdForLastInstallment, - accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, installmentAmountInMultiplesOf); + accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineEmiAmount, installmentAmountInMultiplesOf, loanConfigurableAttributes); } @@ -486,7 +497,7 @@ public class LoanProduct extends AbstractPersistable { final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds, final LoanProductGuaranteeDetails loanProductGuaranteeDetails, final BigDecimal principalThresholdForLastInstallment, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, final boolean canDefineEmiAmount, - final Integer installmentAmountInMultiplesOf) { + final Integer installmentAmountInMultiplesOf, final LoanProductConfigurableAttributes loanProductConfigurableAttributes) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -532,6 +543,11 @@ public class LoanProduct extends AbstractPersistable { for (LoanProductBorrowerCycleVariations borrowerCycleVariations : this.borrowerCycleVariations) { borrowerCycleVariations.updateLoanProduct(this); } + if (loanProductConfigurableAttributes != null) { + this.loanConfigurableAttributes = loanProductConfigurableAttributes; + loanConfigurableAttributes.updateLoanProduct(this); + } + this.loanProducTrancheDetails = new LoanProductTrancheDetails(multiDisburseLoan, maxTrancheCount, outstandingLoanBalance); this.overdueDaysForNPA = overdueDaysForNPA; this.productInterestRecalculationDetails = productInterestRecalculationDetails; @@ -556,6 +572,10 @@ public class LoanProduct extends AbstractPersistable { this.transactionProcessingStrategy = strategy; } + public LoanTransactionProcessingStrategy getRepaymentStrategy() { + return this.transactionProcessingStrategy; + } + public boolean hasCurrencyCodeOf(final String currencyCode) { return this.loanProductRelatedDetail.hasCurrencyCodeOf(currencyCode); } @@ -583,6 +603,14 @@ public class LoanProduct extends AbstractPersistable { return this.accountingRule; } + public void update(final LoanProductConfigurableAttributes loanConfigurableAttributes) { + this.loanConfigurableAttributes = loanConfigurableAttributes; + } + + public LoanProductConfigurableAttributes getLoanProductConfigurableAttributes() { + return this.loanConfigurableAttributes; + } + public Map update(final JsonCommand command, final AprCalculator aprCalculator) { final Map actualChanges = this.loanProductRelatedDetail.update(command, aprCalculator); @@ -751,6 +779,80 @@ public class LoanProduct extends AbstractPersistable { this.holdGuaranteeFunds = newValue; } + final String configurableAttributesChanges = LoanProductConstants.allowAttributeOverridesParamName; + if (command.hasParameter(configurableAttributesChanges)) { + if (!command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName).isJsonNull()) { + actualChanges.put(configurableAttributesChanges, command.jsonFragment(configurableAttributesChanges)); + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean() != this.loanConfigurableAttributes + .getAmortizationBoolean()) { + this.loanConfigurableAttributes.setAmortizationType(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean() != this.loanConfigurableAttributes + .getInterestMethodBoolean()) { + this.loanConfigurableAttributes.setInterestType(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean() != this.loanConfigurableAttributes + .getTransactionProcessingStrategyBoolean()) { + this.loanConfigurableAttributes.setTransactionProcessingStrategyId(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean() != this.loanConfigurableAttributes + .getInterestCalcPeriodBoolean()) { + this.loanConfigurableAttributes.setInterestCalculationPeriodType(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean() != this.loanConfigurableAttributes + .getArrearsToleranceBoolean()) { + this.loanConfigurableAttributes.setInArrearsTolerance(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean() != this.loanConfigurableAttributes + .getRepaymentEveryBoolean()) { + this.loanConfigurableAttributes.setRepaymentEvery(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean() != this.loanConfigurableAttributes + .getGraceOnPrincipalAndInterestPaymentBoolean()) { + this.loanConfigurableAttributes.setGraceOnPrincipalAndInterestPayment(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean()); + } + + if (command.parsedJson().getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean() != this.loanConfigurableAttributes + .getGraceOnArrearsAgingBoolean()) { + this.loanConfigurableAttributes.setGraceOnArrearsAgeing(command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean()); + } + } else { + this.loanConfigurableAttributes = LoanProductConfigurableAttributes.populateDefaultsForConfigurableAttributes(); + this.loanConfigurableAttributes.updateLoanProduct(this); + } + } + if (actualChanges.containsKey(LoanProductConstants.holdGuaranteeFundsParamName)) { if (this.holdGuaranteeFunds) { this.loanProductGuaranteeDetails = LoanProductGuaranteeDetails.createFrom(command); @@ -1077,4 +1179,7 @@ public class LoanProduct extends AbstractPersistable { return preCloseInterestCalculationStrategy; } + public LoanProductRelatedDetail getLoanProductRelatedDetail() { + return loanProductRelatedDetail; + } } \ No newline at end of file diff --git a/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java new file mode 100644 index 000000000..533509c51 --- /dev/null +++ b/mifosng-provider/src/main/java/org/mifosplatform/portfolio/loanproduct/domain/LoanProductConfigurableAttributes.java @@ -0,0 +1,196 @@ +/** + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package org.mifosplatform.portfolio.loanproduct.domain; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +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 +@Table(name = "m_product_loan_configurable_attributes") +public class LoanProductConfigurableAttributes extends AbstractPersistable { + + @ManyToOne + @JoinColumn(name = "loan_product_id", nullable = true) + private LoanProduct loanProduct; + + @Column(name = "amortization_method_enum", nullable = true) + private Boolean amortizationType; + + @Column(name = "interest_method_enum", nullable = true) + private Boolean interestType; + + @Column(name = "loan_transaction_strategy_id", nullable = true) + private Boolean transactionProcessingStrategyId; + + @Column(name = "interest_calculated_in_period_enum", nullable = true) + private Boolean interestCalculationPeriodType; + + @Column(name = "arrearstolerance_amount", nullable = true) + private Boolean inArrearsTolerance; + + @Column(name = "repay_every", nullable = true) + private Boolean repaymentEvery; + + @Column(name = "moratorium", nullable = true) + private Boolean graceOnPrincipalAndInterestPayment; + + @Column(name = "grace_on_arrears_ageing", nullable = true) + private Boolean graceOnArrearsAgeing; + + public static String[] supportedloanConfigurableAttributes = { "amortizationType", "interestType", "transactionProcessingStrategyId", + "interestCalculationPeriodType", "inArrearsTolerance", "repaymentEvery", "graceOnPrincipalAndInterestPayment", + "graceOnArrearsAging" }; + + public static LoanProductConfigurableAttributes createFrom(JsonCommand command) { + + final Boolean amortization = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.amortizationTypeParamName).getAsBoolean(); + final Boolean interestMethod = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestTypeParamName).getAsBoolean(); + final Boolean transactionProcessingStrategy = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.transactionProcessingStrategyIdParamName).getAsBoolean(); + final Boolean interestCalcPeriod = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.interestCalculationPeriodTypeParamName).getAsBoolean(); + final Boolean arrearsTolerance = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.inArrearsToleranceParamName).getAsBoolean(); + final Boolean repaymentEvery = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.repaymentEveryParamName).getAsBoolean(); + final Boolean graceOnPrincipalAndInterestPayment = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnPrincipalAndInterestPaymentParamName).getAsBoolean(); + final Boolean graceOnArrearsAging = command.parsedJson().getAsJsonObject() + .getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName) + .getAsJsonPrimitive(LoanProductConstants.graceOnArrearsAgeingParameterName).getAsBoolean(); + + return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod, + arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging); + } + + public void updateLoanProduct(final LoanProduct loanProduct) { + this.loanProduct = loanProduct; + } + + public static LoanProductConfigurableAttributes populateDefaultsForConfigurableAttributes() { + final Boolean amortization = true; + final Boolean interestMethod = true; + final Boolean transactionProcessingStrategy = true; + final Boolean interestCalcPeriod = true; + final Boolean arrearsTolerance = true; + final Boolean repaymentEvery = true; + final Boolean graceOnPrincipalAndInterestPayment = true; + final Boolean graceOnArrearsAging = true; + + return new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, interestCalcPeriod, + arrearsTolerance, repaymentEvery, graceOnPrincipalAndInterestPayment, graceOnArrearsAging); + } + + public LoanProductConfigurableAttributes(Boolean amortization, Boolean interestMethod, Boolean transactionProcessingStrategy, + Boolean interestCalcPeriod, Boolean arrearsTolerance, Boolean repaymentEvery, Boolean graceOnPrincipalAndInterestPayment, + Boolean graceOnArrearsAging) { + this.amortizationType = amortization; + this.interestType = interestMethod; + this.inArrearsTolerance = arrearsTolerance; + this.graceOnArrearsAgeing = graceOnArrearsAging; + this.interestCalculationPeriodType = interestCalcPeriod; + this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment; + this.repaymentEvery = repaymentEvery; + this.transactionProcessingStrategyId = transactionProcessingStrategy; + } + + protected LoanProductConfigurableAttributes() { + + } + + public static String[] getAllowedLoanConfigurableAttributes() { + return supportedloanConfigurableAttributes; + } + + public LoanProduct getLoanProduct() { + return loanProduct; + } + + public Boolean getAmortizationBoolean() { + return amortizationType; + } + + public Boolean getInterestMethodBoolean() { + return interestType; + } + + public Boolean getTransactionProcessingStrategyBoolean() { + return transactionProcessingStrategyId; + } + + public Boolean getInterestCalcPeriodBoolean() { + return interestCalculationPeriodType; + } + + public Boolean getArrearsToleranceBoolean() { + return inArrearsTolerance; + } + + public Boolean getRepaymentEveryBoolean() { + return repaymentEvery; + } + + public Boolean getGraceOnPrincipalAndInterestPaymentBoolean() { + return graceOnPrincipalAndInterestPayment; + } + + public Boolean getGraceOnArrearsAgingBoolean() { + return graceOnArrearsAgeing; + } + + public void setLoanProduct(LoanProduct loanProduct) { + this.loanProduct = loanProduct; + } + + public void setAmortizationType(Boolean amortizationType) { + this.amortizationType = amortizationType; + } + + public void setInterestType(Boolean interestType) { + this.interestType = interestType; + } + + public void setTransactionProcessingStrategyId(Boolean transactionProcessingStrategyId) { + this.transactionProcessingStrategyId = transactionProcessingStrategyId; + } + + public void setInterestCalculationPeriodType(Boolean interestCalculationPeriodType) { + this.interestCalculationPeriodType = interestCalculationPeriodType; + } + + public void setInArrearsTolerance(Boolean inArrearsTolerance) { + this.inArrearsTolerance = inArrearsTolerance; + } + + public void setRepaymentEvery(Boolean repaymentEvery) { + this.repaymentEvery = repaymentEvery; + } + + public void setGraceOnPrincipalAndInterestPayment(Boolean graceOnPrincipalAndInterestPayment) { + this.graceOnPrincipalAndInterestPayment = graceOnPrincipalAndInterestPayment; + } + + public void setGraceOnArrearsAgeing(Boolean graceOnArrearsAgeing) { + this.graceOnArrearsAgeing = graceOnArrearsAgeing; + } + +} 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 d3704437b..3bb708784 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 @@ -514,4 +514,57 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche public void updateNumberOfRepayments(Integer numberOfRepayments) { this.numberOfRepayments = numberOfRepayments; } + + public Integer getGraceOnPrincipalPayment() { + return graceOnPrincipalPayment; + } + + public void setGraceOnPrincipalPayment(Integer graceOnPrincipalPayment) { + this.graceOnPrincipalPayment = graceOnPrincipalPayment; + } + + public Integer getGraceOnInterestPayment() { + return graceOnInterestPayment; + } + + public void setGraceOnInterestPayment(Integer graceOnInterestPayment) { + this.graceOnInterestPayment = graceOnInterestPayment; + } + + public Integer getGraceOnArrearsAgeing() { + return graceOnArrearsAgeing; + } + + public void setGraceOnArrearsAgeing(Integer graceOnArrearsAgeing) { + this.graceOnArrearsAgeing = graceOnArrearsAgeing; + } + + public void setInterestMethod(InterestMethod interestMethod) { + this.interestMethod = interestMethod; + } + + public void setInterestCalculationPeriodMethod(InterestCalculationPeriodMethod interestCalculationPeriodMethod) { + this.interestCalculationPeriodMethod = interestCalculationPeriodMethod; + } + + public void setRepayEvery(Integer repayEvery) { + this.repayEvery = repayEvery; + } + + public void setRepaymentPeriodFrequencyType(PeriodFrequencyType repaymentPeriodFrequencyType) { + this.repaymentPeriodFrequencyType = repaymentPeriodFrequencyType; + } + + public void setAmortizationMethod(AmortizationMethod amortizationMethod) { + this.amortizationMethod = amortizationMethod; + } + + public void setInArrearsTolerance(BigDecimal inArrearsTolerance) { + this.inArrearsTolerance = inArrearsTolerance; + } + + public BigDecimal getArrearsTolerance() { + return this.inArrearsTolerance; + } + } \ No newline at end of file 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 fd9893351..ae9193faa 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 @@ -32,6 +32,7 @@ import org.mifosplatform.portfolio.loanproduct.domain.LoanProductValueConditionT import org.mifosplatform.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -78,7 +79,7 @@ public final class LoanProductDataValidator { LoanProductConstants.minimumGuaranteeFromOwnFundsParamName, LoanProductConstants.principalThresholdForLastInstallmentParamName, LoanProductConstants.accountMovesOutOfNPAOnlyOnArrearsCompletionParamName, LoanProductConstants.canDefineEmiAmountParamName, LoanProductConstants.installmentAmountInMultiplesOfParamName, - LoanProductConstants.preClosureInterestCalculationStrategyParamName)); + LoanProductConstants.preClosureInterestCalculationStrategyParamName, LoanProductConstants.allowAttributeOverridesParamName)); private final FromJsonHelper fromApiJsonHelper; @@ -441,9 +442,39 @@ public final class LoanProductDataValidator { validateMultiDisburseLoanData(baseDataValidator, element); + // validateLoanConfigurableAttributes(baseDataValidator,element); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } + private void validateLoanConfigurableAttributes(final DataValidatorBuilder baseDataValidator, final JsonElement element) { + + if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.allowAttributeOverridesParamName, element)) { + + final JsonObject object = element.getAsJsonObject().getAsJsonObject(LoanProductConstants.allowAttributeOverridesParamName); + + Integer length = LoanProductConfigurableAttributes.supportedloanConfigurableAttributes.length; + + Boolean bool[] = new Boolean[length]; + + for (int i = 0; i < length; i++) { + /* Validate the attribute names */ + if (!this.fromApiJsonHelper.parameterExists(LoanProductConfigurableAttributes.supportedloanConfigurableAttributes[i], + object)) { + baseDataValidator.reset().parameter(LoanProductConstants.allowAttributeOverridesParamName) + .failWithCode("invalid.loan.configuration.attribute.passed"); + } else { + bool[i] = this.fromApiJsonHelper.extractBooleanNamed( + LoanProductConfigurableAttributes.supportedloanConfigurableAttributes[i], object); + /* Validate the boolean value */ + baseDataValidator.reset().parameter(LoanProductConstants.allowAttributeOverridesParamName).value(bool[i]) + .ignoreIfNull().validateForBooleanValue(); + } + + } + } + } + private void validateMultiDisburseLoanData(final DataValidatorBuilder baseDataValidator, final JsonElement element) { Boolean multiDisburseLoan = false; if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.multiDisburseLoanParameterName, element)) { @@ -901,6 +932,8 @@ public final class LoanProductDataValidator { validateMultiDisburseLoanData(baseDataValidator, element); + // validateLoanConfigurableAttributes(baseDataValidator,element); + throwExceptionIfValidationWarningsExist(dataValidationErrors); } 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 745c11818..61343416e 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 @@ -28,6 +28,7 @@ import org.mifosplatform.portfolio.loanproduct.data.LoanProductBorrowerCycleVari import org.mifosplatform.portfolio.loanproduct.data.LoanProductData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductGuaranteeData; import org.mifosplatform.portfolio.loanproduct.data.LoanProductInterestRecalculationData; +import org.mifosplatform.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.mifosplatform.portfolio.loanproduct.domain.LoanProductParamType; import org.mifosplatform.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.springframework.beans.factory.annotation.Autowired; @@ -76,7 +77,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final String sql = "select " + rm.schema() + " where bc.loan_product_id=? order by bc.borrower_cycle_number,bc.value_condition"; return this.jdbcTemplate.query(sql, rm, new Object[] { loanProductId }); } - + @Override public Collection retrieveAllLoanProducts() { @@ -170,12 +171,16 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lpg.id as lpgId, lpg.mandatory_guarantee as mandatoryGuarantee, " + "lpg.minimum_guarantee_from_own_funds as minimumGuaranteeFromOwnFunds, lpg.minimum_guarantee_from_guarantor_funds as minimumGuaranteeFromGuarantor, " + "lp.account_moves_out_of_npa_only_on_arrears_completion as accountMovesOutOfNPAOnlyOnArrearsCompletion, " - + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId " + + "curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, curr.display_symbol as currencyDisplaySymbol, lp.external_id as externalId, " + + "lca.id as lcaId, lca.amortization_method_enum as amortizationBoolean, lca.interest_method_enum as interestMethodConfigBoolean, " + + "lca.loan_transaction_strategy_id as transactionProcessingStrategyBoolean,lca.interest_calculated_in_period_enum as interestCalcPeriodBoolean, lca.arrearstolerance_amount as arrearsToleranceBoolean, " + + "lca.repay_every as repaymentFrequencyBoolean, lca.moratorium as graceOnPrincipalAndInterestBoolean, lca.grace_on_arrears_ageing as graceOnArrearsAgingBoolean " + " from m_product_loan lp " + " 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 m_product_loan_guarantee_details lpg on lpg.loan_product_id=lp.id " + " left join ref_loan_transaction_processing_strategy ltps on ltps.id = lp.loan_transaction_strategy_id" + + " left join m_product_loan_configurable_attributes lca on lca.loan_product_id = lp.id " + " join m_currency curr on curr.code = lp.currency_code"; } @@ -307,6 +312,20 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo restFrequencyDate, isArrearsBasedOnOriginalSchedule, preCloseInterestCalculationStrategy); } + final boolean amortization = rs.getBoolean("amortizationBoolean"); + final boolean interestMethod = rs.getBoolean("interestMethodConfigBoolean"); + final boolean transactionProcessingStrategy = rs.getBoolean("transactionProcessingStrategyBoolean"); + final boolean interestCalcPeriod = rs.getBoolean("interestCalcPeriodBoolean"); + final boolean arrearsTolerance = rs.getBoolean("arrearsToleranceBoolean"); + final boolean repaymentFrequency = rs.getBoolean("repaymentFrequencyBoolean"); + final boolean graceOnPrincipalAndInterest = rs.getBoolean("graceOnPrincipalAndInterestBoolean"); + final boolean graceOnArrearsAging = rs.getBoolean("graceOnArrearsAgingBoolean"); + + LoanProductConfigurableAttributes allowAttributeOverrides = null; + + allowAttributeOverrides = new LoanProductConfigurableAttributes(amortization, interestMethod, transactionProcessingStrategy, + interestCalcPeriod, arrearsTolerance, repaymentFrequency, graceOnPrincipalAndInterest, graceOnArrearsAging); + final boolean holdGuaranteeFunds = rs.getBoolean("holdGuaranteeFunds"); LoanProductGuaranteeData loanProductGuaranteeData = null; if (holdGuaranteeFunds) { @@ -332,7 +351,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo graceOnArrearsAgeing, overdueDaysForNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, minimumDaysBetweenDisbursalAndFirstRepayment, holdGuaranteeFunds, loanProductGuaranteeData, principalThresholdForLastInstallment, accountMovesOutOfNPAOnlyOnArrearsCompletion, canDefineInstallmentAmount, - installmentAmountInMultiplesOf); + installmentAmountInMultiplesOf, allowAttributeOverrides); } } 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 17c099ea4..530894a9c 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 @@ -110,7 +110,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro MifosEntityAccessType.OFFICE_ACCESS_TO_LOAN_PRODUCTS, MifosEntityType.LOAN_PRODUCT, loanproduct.getId()); - + return new CommandProcessingResultBuilder() // .withCommandId(command.commandId()) // .withEntityId(loanproduct.getId()) // diff --git a/mifosng-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql b/mifosng-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql new file mode 100644 index 000000000..7db8d65b8 --- /dev/null +++ b/mifosng-provider/src/main/resources/sql/migrations/core_db/V253__product_loan_configurable_attributes.sql @@ -0,0 +1,21 @@ +CREATE TABLE `m_product_loan_configurable_attributes` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT, + `loan_product_id` BIGINT NOT NULL, + `amortization_method_enum` TINYINT NOT NULL DEFAULT '1', + `interest_method_enum` TINYINT NOT NULL DEFAULT '1', + `loan_transaction_strategy_id` TINYINT NOT NULL DEFAULT '1', + `interest_calculated_in_period_enum` TINYINT NOT NULL DEFAULT '1', + `arrearstolerance_amount` TINYINT NOT NULL DEFAULT '1', + `repay_every` TINYINT NOT NULL DEFAULT '1', + `moratorium` TINYINT NOT NULL DEFAULT '1', + `grace_on_arrears_ageing` TINYINT NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + KEY `fk_m_product_loan_configurable_attributes_0001` (`loan_product_id`), + CONSTRAINT `fk_m_product_loan_configurable_attributes_0001` FOREIGN KEY (`loan_product_id`) REFERENCES `m_product_loan` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + + + INSERT into `m_product_loan_configurable_attributes` +(loan_product_id,amortization_method_enum,interest_method_enum,loan_transaction_strategy_id, +interest_calculated_in_period_enum,arrearstolerance_amount,repay_every,moratorium,grace_on_arrears_ageing) +(select pl.id,'1','1','1','1','1','1','1','1' from `m_product_loan` pl ); \ No newline at end of file