Return-Path: X-Original-To: apmail-fineract-dev-archive@minotaur.apache.org Delivered-To: apmail-fineract-dev-archive@minotaur.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 6C69F19FE7 for ; Tue, 12 Apr 2016 08:02:43 +0000 (UTC) Received: (qmail 53175 invoked by uid 500); 12 Apr 2016 08:02:43 -0000 Delivered-To: apmail-fineract-dev-archive@fineract.apache.org Received: (qmail 53137 invoked by uid 500); 12 Apr 2016 08:02:43 -0000 Mailing-List: contact dev-help@fineract.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@fineract.incubator.apache.org Delivered-To: mailing list dev@fineract.incubator.apache.org Received: (qmail 53125 invoked by uid 99); 12 Apr 2016 08:02:42 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd3-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 12 Apr 2016 08:02:42 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd3-us-west.apache.org (ASF Mail Server at spamd3-us-west.apache.org) with ESMTP id 63D6B180227 for ; Tue, 12 Apr 2016 08:02:42 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd3-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: 1.559 X-Spam-Level: * X-Spam-Status: No, score=1.559 tagged_above=-999 required=6.31 tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, HTML_MESSAGE=2, KAM_LOTSOFHASH=0.25, RCVD_IN_DNSWL_LOW=-0.7, RCVD_IN_MSPIKE_H2=-0.001, T_REMOTE_IMAGE=0.01] autolearn=disabled Authentication-Results: spamd3-us-west.apache.org (amavisd-new); dkim=pass (2048-bit key) header.d=confluxtechnologies-com.20150623.gappssmtp.com Received: from mx1-lw-us.apache.org ([10.40.0.8]) by localhost (spamd3-us-west.apache.org [10.40.0.10]) (amavisd-new, port 10024) with ESMTP id t47-JjwC9sPC for ; Tue, 12 Apr 2016 08:02:33 +0000 (UTC) Received: from mail-pf0-f173.google.com (mail-pf0-f173.google.com [209.85.192.173]) by mx1-lw-us.apache.org (ASF Mail Server at mx1-lw-us.apache.org) with ESMTPS id 2DA395FAEA for ; Tue, 12 Apr 2016 08:02:32 +0000 (UTC) Received: by mail-pf0-f173.google.com with SMTP id 184so9370952pff.0 for ; Tue, 12 Apr 2016 01:02:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=confluxtechnologies-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:references:in-reply-to:subject:date:message-id :mime-version:thread-index:content-language; bh=W2PDZTQdQiOIRgNS095a7fSIAHN1YdGTCDDEYwgOCtk=; b=Rji67MYwU3TQ1+8KzvfNiq+6eJFErJyakyuL6z4jlGTrJ3QmieKO9cvrM8PwEgb6pE h7qZ6LPrg3/4OHnby8tx2L8FVYY0/siw2ZVc9hOug1cmS9heAl6kYDVbpUktm+FIWBHr gTeq6xAo/VOjW0G/n9j/j5y+WxKCDptI9sk6h2Zyr2DUdiERm+TwRtPvaR3osUghBBcC EA8OkkS5v7ZkL8+4MseZGnL6VhUk6P6Koz5pPwCSTXu7JqEeHZcDHyBK9x4UQITdslNe w0iOqttrm5+7tuZsTJpkPvi2wupqOfvmR/PpJA/7y8t5SVnacJ+0+DB1D1bDOQ1vIVFt p8Mw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:references:in-reply-to:subject:date :message-id:mime-version:thread-index:content-language; bh=W2PDZTQdQiOIRgNS095a7fSIAHN1YdGTCDDEYwgOCtk=; b=PJ50fEFbFeAMI5VOYsbN3hk37RlmwYpPxkUeJFPTiiHfUSMNuIg4m1GtsZzlNdSZnF UYNNm00DP7XmJnK/PPw2+UFcn8ZOM0AFN/feDScWo9hOJ2Zzlf/IHf/jxxMn7pYMo4Jv 2sgP9cXgh1QiVBNklNZ3fiHc34YgbHyxL6u0jverHqWa4ehRY/7yEJ+M0ZySPoMVJ4vf Pp1pV4pGh2RVgE809arJjdEhse173/87uZLnJLOVjp9sxbyloz8LsgqHqFNDFL3vWt6S aCoSj7bd4DILm7n3mnUIZ4iETG7Lw0s/JSfS/SoyZSc5uoe3VL1KzjmuoOORhnKIWscI R1dg== X-Gm-Message-State: AOPr4FX1fSGcrF7Z7xsWTdDhTWe0C/+7RzuIK1/1SnFPvUsoQq/s1QUjV7DhqT/98kzU4A== X-Received: by 10.98.40.200 with SMTP id o191mr2719875pfo.83.1460448150914; Tue, 12 Apr 2016 01:02:30 -0700 (PDT) Received: from ConfluxAdi ([106.51.39.37]) by smtp.gmail.com with ESMTPSA id k65sm41320803pfb.30.2016.04.12.01.02.26 (version=TLSv1/SSLv3 cipher=OTHER); Tue, 12 Apr 2016 01:02:30 -0700 (PDT) From: "Adi Raju" To: "'Agris Varpins'" , "'Sander van der Heyden'" Cc: "'Ed Cable'" , , "'robert wizglobal'" , "'Zack Wizglobal'" , , "'Andris Kaneps'" , "'Philippe Storm'" , =?UTF-8?Q?'Markus_Gei=C3=9F'?= References: In-Reply-To: Subject: RE: Clarification on Validator Classes for Multiple Rescheduling of a Loan Date: Tue, 12 Apr 2016 13:32:30 +0530 Message-ID: <006201d19491$ac05e010$0411a030$@confluxtechnologies.com> MIME-Version: 1.0 Content-Type: multipart/alternative; boundary="----=_NextPart_000_0063_01D194BF.C5CCE920" X-Mailer: Microsoft Outlook 16.0 Thread-Index: AQG41ctXOIfUALYG7MWSZ7cuv9nQZAIrKioMArQyfLEBbmNBpwLfGED0Amr+s+YB84JotZ9LHJ1Q Content-Language: en-in ------=_NextPart_000_0063_01D194BF.C5CCE920 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable As mentioned by Sander: =E2=80=9CThe main reason we put in the restrictions around allowing only = one reschedule, was to also enable users to undo them easily if they = were made by mistake. I think that is something that can be solved, but = would require a bit of extra work ensuring that the correct old = schedules are grabbed and reapplied to the loan.=E2=80=9D =20 Until this additional work is done, I wouldn=E2=80=99t recommend current = solution to be merged. =20 Regards, Adi =20 From: Agris Varpins [mailto:agris.varpins@mtgcapital.ch]=20 Sent: 12 April 2016 13:14 To: Sander van der Heyden Cc: Ed Cable ; dev@fineract.incubator.apache.org; = robert wizglobal ; Zack Wizglobal = ; pramod@confluxtechnologies.com Nuthakki = ; Andris Kaneps ; = Philippe Storm ; Markus Gei=C3=9F = ; Adi Raju Subject: Re: Clarification on Validator Classes for Multiple = Rescheduling of a Loan =20 Good morning, gentlemen! So where do we stand with this update? Ed, has your team done some = testing of the update to see if it works properly with the test clients? = When we tested for our purposes, it seemed to work as we expected - = loans could be rescheduled multiple times and reschedule could be done = without undoing previously entered payments. Granted, we did not test = how this fix affects, for example, accounting function, Overall, even if = some additional fix is necessary to the Zack's product, that still would = be doable.I believe it is in all interests to achieve that Mifos offers = this fuction to its user community including ourselves. Please let us now where do we stand at the moment and what are the = prospects for this fix so that we can plan accordingly.=20 Best regards, Agris =20 On Mon, Apr 11, 2016 at 12:29 PM, Sander van der Heyden = > = wrote: Hi all, =20 Ignore my response, I was responding to the wrong thread, and not paying = attention, still early here I guess...=20 =20 In terms of rescheduling, I think the current solution would need to be = tested very carefully before it can be considered stable (or not), and = therefore I'd recommend doing that before we merge the commit. Might = also be good to add one or 2 test cases for the multiple reschedules to = ensure that we have it covered there as well. =20 S Sander van der Heyden CTO Musoni Services =20 Mobile (NL): +31 (0)6 14239505 Skype: s.vdheyden Website: musonisystem.com Follow us on Twitter!=20 Postal address: Hillegomstraat 12-14, office 0.09, 1058 LS, Amsterdam, = The Netherlands =20 On Mon, Apr 11, 2016 at 11:17 AM, Sander van der Heyden = > = wrote: Hi Agris, =20 You can already do all of this by using the current datatables, where = you can add all fields necessary to the clients data that you want to = capture. So the update is not really a requirement to get this done, a = large number of MFI's are already using the system with all of these = fields added in. =20 Thanks, Sander Sander van der Heyden CTO Musoni Services =20 Mobile (NL): +31 (0)6 14239505 Skype: s.vdheyden Website: musonisystem.com Follow us on Twitter!=20 Postal address: Hillegomstraat 12-14, office 0.09, 1058 LS, Amsterdam, = The Netherlands =20 On Mon, Apr 11, 2016 at 10:58 AM, Agris Varpins = > = wrote: Good morning, all! Thank you all for you inputs! So what is the verdict regarding this = update? Will it work? Or if not, can it be easily adjusted and perfected = so that it does? I cannot overstate hot important this fix is for us and = we are really looking to solve this issue as soon as possible,=20 Looking forward to you feedback. Best regards, Agris =20 On Mon, Apr 11, 2016 at 9:09 AM, Sander van der Heyden = > = wrote: Hi all =20 The main reason we put in the restrictions around allowing only one = reschedule, was to also enable users to undo them easily if they were = made by mistake. I think that is something that can be solved, but would = require a bit of extra work ensuring that the correct old schedules are = grabbed and reapplied to the loan. =20 S Sander van der Heyden CTO Musoni Services =20 Mobile (NL): +31 (0)6 14239505 Skype: s.vdheyden Website: musonisystem.com Follow us on Twitter!=20 Postal address: Hillegomstraat 12-14, office 0.09, 1058 LS, Amsterdam, = The Netherlands =20 On Fri, Apr 8, 2016 at 5:46 PM, Ed Cable > wrote: Sander, have you had a chance to review this thread?=20 =20 Andris' team is in need of this feature and wanted to get feedback on = the approach they've taken to see if they can continue with that or they = need to follow the path that was proposed by Pramod. =20 Ed = = =20 =20 On Wed, Apr 6, 2016 at 9:38 AM, Ed Cable > wrote: Zack and Robert have been working on contributing a fix to add the = ability to reschedule a loan multiple times. =20 They have taken a different approach than what Pramod had previously = outlined so we wanted to discuss their proposed fix with Sander and his = team who have provided the initial fix to reschedule a loan a single = time. =20 Ed =20 On Mon, Apr 4, 2016 at 9:32 PM, Adi Raju = > = wrote: Hi Robert, =20 Validator classes generally only perform API parameter validations, in = other words they are the first check point before proceeding to more = costlier DB or calculation tasks. All that is done in this change is that the validation at the first = check point is removed. These checkpoints were added by earlier developers because they = haven=E2=80=99t addressed those scenarios in further calculations. If the core code works for multi-reschedule, they wouldn=E2=80=99t have = put this check in the first place. =20 I really doubt this solution is working as it is supposed to be. Have = you been able to test it against expected schedule and its values post = reschedule action? Does other like retrieve/approve/reject reschedule = APIs work with this solution? =20 +Sander, who can help us with more clarifications on why such = validations were added. =20 Regards, Adi =20 =20 From: robert wizglobal [mailto:robert@wizglobal.co.ke = ]=20 Sent: 04 April 2016 22:13 To: Adi Raju > Cc: Zack Wizglobal >; Ed Cable = >; = pramod@confluxtechnologies.com = ; Agris = Varpins >; Andris Kaneps = >; Philippe Storm = > Subject: Re: Mifos fix =20 Hello Adi =20 It Was Not a Major Fix Below is the Change i did on the = LoanRescheduleRequestDataValidator Class =20 /** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 = =20 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data; =20 import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; import java.util.Map; =20 import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import = org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import = org.apache.fineract.infrastructure.core.exception.InvalidJsonException; import = org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati= onException; import = org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import = org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleIns= tallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import = org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansA= piConstants; import = org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanResch= eduleRequest; import = org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanResc= heduleRequestReadPlatformService; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; =20 import com.google = .gson.JsonElement; import com.google = .gson.reflect.TypeToken; =20 @Component public class LoanRescheduleRequestDataValidator { =20 private final FromJsonHelper fromJsonHelper; private final LoanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService; =20 @Autowired public LoanRescheduleRequestDataValidator(FromJsonHelper = fromJsonHelper, LoanRescheduleRequestReadPlatformService = loanRescheduleRequestReadPlatformService) { this.fromJsonHelper =3D fromJsonHelper; this.loanRescheduleRequestReadPlatformService =3D = loanRescheduleRequestReadPlatformService; } =20 /** * Validates the request to create a new loan reschedule entry *=20 * @param jsonCommand * the JSON command object (instance of the JsonCommand = class) * @return void **/ public void validateForCreateAction(final JsonCommand jsonCommand, = final Loan loan) { =20 final String jsonString =3D jsonCommand.json(); =20 if (StringUtils.isBlank(jsonString)) { throw new = InvalidJsonException(); } =20 final Type typeToken =3D new TypeToken>() = {}.getType(); this.fromJsonHelper .checkForUnsupportedParameters(typeToken, jsonString, = RescheduleLoansApiConstants.CREATE_REQUEST_DATA_PARAMETERS); =20 final List dataValidationErrors =3D new = ArrayList<>(); final DataValidatorBuilder dataValidatorBuilder =3D new = DataValidatorBuilder(dataValidationErrors).resource(StringUtils .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); =20 final JsonElement jsonElement =3D jsonCommand.parsedJson(); =20 if (!loan.status().isActive()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loa= n.is.not.active", "Loan is not active"); } =20 final Long loanId =3D = this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdPa= ramName, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanId= ParamName).value(loanId).notNull() .integerGreaterThanZero(); =20 final LocalDate submittedOnDate =3D = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.sub= mittedOnDateParamName, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submit= tedOnDateParamName).value(submittedOnDate).notNull(); =20 if (submittedOnDate !=3D null && = loan.getDisbursementDate().isAfter(submittedOnDate)) { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submit= tedOnDateParamName) .failWithCode("before.loan.disbursement.date", = "Submission date cannot be before the loan disbursement date"); } =20 final LocalDate rescheduleFromDate =3D = this.fromJsonHelper.extractLocalDateNamed( RescheduleLoansApiConstants.rescheduleFromDateParamName, = jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleFromDateParamName).value(rescheduleFromDate).notNull(); =20 final Integer graceOnPrincipal =3D = this.fromJsonHelper.extractIntegerWithLocaleNamed( RescheduleLoansApiConstants.graceOnPrincipalParamName, = jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceO= nPrincipalParamName).value(graceOnPrincipal) .ignoreIfNull().integerGreaterThanZero(); =20 final Integer graceOnInterest =3D = this.fromJsonHelper.extractIntegerWithLocaleNamed( RescheduleLoansApiConstants.graceOnInterestParamName, = jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceO= nInterestParamName).value(graceOnInterest).ignoreIfNull() .integerGreaterThanZero(); =20 final Integer extraTerms =3D = this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConst= ants.extraTermsParamName, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraT= ermsParamName).value(extraTerms).ignoreIfNull() .integerGreaterThanZero(); =20 final Long rescheduleReasonId =3D = this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.reschedu= leReasonIdParamName, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleReasonIdParamName).value(rescheduleReasonId).notNull() .integerGreaterThanZero(); =20 final String rescheduleReasonComment =3D = this.fromJsonHelper.extractStringNamed( = RescheduleLoansApiConstants.rescheduleReasonCommentParamName, = jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleReasonCommentParamName).value(rescheduleReasonComment) .ignoreIfNull().notExceedingLengthOf(500); =20 final LocalDate adjustedDueDate =3D = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adj= ustedDueDateParamName, jsonElement); =20 if (adjustedDueDate !=3D null && rescheduleFromDate !=3D null && = adjustedDueDate.isBefore(rescheduleFromDate)) { dataValidatorBuilder .reset() = .parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) = .failWithCode("adjustedDueDate.before.rescheduleFromDate", "Adjusted due date cannot be before the = reschedule from date"); } =20 // at least one of the following must be provided =3D> = graceOnPrincipal, // graceOnInterest, extraTerms, newInterestRate if = (!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOn= PrincipalParamName, jsonElement) && = !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnI= nterestParamName, jsonElement) && = !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTer= msParamName, jsonElement) && = !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInter= estRateParamName, jsonElement) && = !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjusted= DueDateParamName, jsonElement)) { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceO= nPrincipalParamName).notNull(); } =20 if (rescheduleFromDate !=3D null) { LoanRepaymentScheduleInstallment installment =3D = loan.getRepaymentScheduleInstallment(rescheduleFromDate); =20 if (installment =3D=3D null) { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleFromDateParamName) = .failWithCode("repayment.schedule.installment.does.not.exist", = "Repayment schedule installment does not exist"); } /* if (installment !=3D null && installment.isObligationsMet()) = { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleFromDateParamName) = .failWithCode("repayment.schedule.installment.obligation.met", = "Repayment schedule installment obligation met"); } */ /* if (installment !=3D null && installment.isPartlyPaid()) { =20 = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.resche= duleFromDateParamName) = .failWithCode("repayment.schedule.installment.partly.paid", "Repayment = schedule installment is partly paid"); } */ } =20 if (loanId !=3D null) { List loanRescheduleRequestData = =3D this.loanRescheduleRequestReadPlatformService .readLoanRescheduleRequests(loanId, = LoanStatus.APPROVED.getValue()); /* //commented this for loan reshedule=20 if (loanRescheduleRequestData.size() > 0) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loa= n.already.rescheduled", "The loan can only be rescheduled once."); } */ } if(loan.isMultiDisburmentLoan()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(Resc= heduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCod= e, "Loan rescheduling is not supported for = multidisbursement loans"); } =20 if(loan.isInterestRecalculationEnabledForProduct()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(Resc= heduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErr= orCode, "Loan rescheduling is not supported for the loan = product with interest recalculation enabled"); } =20 if (!dataValidationErrors.isEmpty()) { throw new = PlatformApiDataValidationException(dataValidationErrors); } } =20 /** * Validates a user request to approve a loan reschedule request *=20 * @param jsonCommand * the JSON command object (instance of the JsonCommand = class) * @return void **/ public void validateForApproveAction(final JsonCommand jsonCommand, = LoanRescheduleRequest loanRescheduleRequest) { final String jsonString =3D jsonCommand.json(); =20 if (StringUtils.isBlank(jsonString)) { throw new = InvalidJsonException(); } =20 final Type typeToken =3D new TypeToken>() = {}.getType(); this.fromJsonHelper.checkForUnsupportedParameters(typeToken, = jsonString, = RescheduleLoansApiConstants.APPROVE_REQUEST_DATA_PARAMETERS); =20 final List dataValidationErrors =3D new = ArrayList<>(); final DataValidatorBuilder dataValidatorBuilder =3D new = DataValidatorBuilder(dataValidationErrors).resource(StringUtils .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); =20 final JsonElement jsonElement =3D jsonCommand.parsedJson(); =20 final LocalDate approvedOnDate =3D = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.app= rovedOnDateParam, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approv= edOnDateParam).value(approvedOnDate).notNull(); =20 if (approvedOnDate !=3D null && = loanRescheduleRequest.getSubmittedOnDate().isAfter(approvedOnDate)) { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approv= edOnDateParam) .failWithCode("before.submission.date", "Approval = date cannot be before the request submission date."); } =20 LoanRescheduleRequestStatusEnumData = loanRescheduleRequestStatusEnumData =3D = LoanRescheduleRequestEnumerations .status(loanRescheduleRequest.getStatusEnum()); =20 if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( "request.is.not.in = .submitted.and.pending.state", "Loan reschedule request approval is not allowed. " + "Loan reschedule request is not in = submitted and pending approval state."); } =20 LocalDate rescheduleFromDate =3D = loanRescheduleRequest.getRescheduleFromDate(); final Loan loan =3D loanRescheduleRequest.getLoan(); =20 if (loan !=3D null) { Long loanId =3D loan.getId(); =20 if (!loan.status().isActive()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loa= n.is.not.active", "Loan is not active"); } =20 if (rescheduleFromDate !=3D null) { LoanRepaymentScheduleInstallment installment =3D = loan.getRepaymentScheduleInstallment(rescheduleFromDate); =20 if (installment =3D=3D null) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( = "loan.repayment.schedule.installment.does.not.exist", "Repayment = schedule installment does not exist"); } /* if (installment !=3D null && = installment.isObligationsMet()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( "loan.repayment.schedule.installment." + = "obligation.met", "Repayment schedule installment obligation met"); } */ } =20 if (loanId !=3D null) { List = loanRescheduleRequestData =3D = this.loanRescheduleRequestReadPlatformService .readLoanRescheduleRequests(loanId, = LoanStatus.APPROVED.getValue()); /* if (loanRescheduleRequestData.size() > 0) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loa= n.already.rescheduled", "The loan can only be rescheduled once."); } */ } } =20 if (!dataValidationErrors.isEmpty()) { throw new = PlatformApiDataValidationException(dataValidationErrors); } } =20 /** * Validates a user request to reject a loan reschedule request *=20 * @param jsonCommand * the JSON command object (instance of the JsonCommand = class) * @return void **/ public void validateForRejectAction(final JsonCommand jsonCommand, = LoanRescheduleRequest loanRescheduleRequest) { final String jsonString =3D jsonCommand.json(); =20 if (StringUtils.isBlank(jsonString)) { throw new = InvalidJsonException(); } =20 final Type typeToken =3D new TypeToken>() = {}.getType(); this.fromJsonHelper .checkForUnsupportedParameters(typeToken, jsonString, = RescheduleLoansApiConstants.REJECT_REQUEST_DATA_PARAMETERS); =20 final List dataValidationErrors =3D new = ArrayList<>(); final DataValidatorBuilder dataValidatorBuilder =3D new = DataValidatorBuilder(dataValidationErrors).resource(StringUtils .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); =20 final JsonElement jsonElement =3D jsonCommand.parsedJson(); =20 final LocalDate rejectedOnDate =3D = this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rej= ectedOnDateParam, jsonElement); = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.reject= edOnDateParam).value(rejectedOnDate).notNull(); =20 if (rejectedOnDate !=3D null && = loanRescheduleRequest.getSubmittedOnDate().isAfter(rejectedOnDate)) { = dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.reject= edOnDateParam) .failWithCode("before.submission.date", "Rejection = date cannot be before the request submission date."); } =20 LoanRescheduleRequestStatusEnumData = loanRescheduleRequestStatusEnumData =3D = LoanRescheduleRequestEnumerations .status(loanRescheduleRequest.getStatusEnum()); =20 if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) { = dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( "request.is.not.in = .submitted.and.pending.state", "Loan reschedule request rejection is not allowed. " + "Loan reschedule request is not in = submitted and pending approval state."); } =20 if (!dataValidationErrors.isEmpty()) { throw new = PlatformApiDataValidationException(dataValidationErrors); } } } =20 =20 On Sat, Apr 2, 2016 at 5:14 AM, Adi Raju = > = wrote: Hi Robert, Please send either a pull request or share your fork and branch details = for us to have a look at code changes. Also if possible send us a short = description of your technical solution. Regards, Adi On 01-Apr-2016 7:00 pm, "Zack Wizglobal" > wrote: Hi Ed, =20 Thanks for the call. Robert copied here is our key developer for the = Mifos System and he will be able to answer all your questions. Robert here we have a team from Mifos who would want to know how we = implemented the loan reschedule. =20 --=20 Kind Regards, Zack Githinji Systems Developer Wizglobal Kenya P.O. BOX 21373-00100 Nairobi. Mobile: +254 (0) 722 649199 = = =20 zack@wizglobal.co.ke = =20 www.wizglobal.co.ke = =20 =20 On 24 Mar 2016, at 21:06, Andris Kaneps > wrote: =20 Dear Zack, Ed, Adi and Pramod,=20 As you may know Mifos currently has a problem with rescheduling function = - we can't reschedule a loan more than once and its impossible to = reschedule a loan if any repayment has been entered.=20 This function is crucial for Watu Credit loan product so we have = commissioned a Nairobi based software developer Wizglobal (represented = by Zack Githinji) to fix the problem. The fix currently is complete and = we have done preliminary testing.=20 As discussed with Ed, we would like to contribute the fix to Mifos = community so that the problem is solved in next Mifos update.=20 So Adi and Pramod, could you please get in touch directly with Zack to = discuss all the technical details? Kind regards, Andris Kaneps=20 =20 =20 =20 =20 =20 --=20 Ed Cable Director of Community Programs, Mifos Initiative edcable@mifos.org = | Skype: edcable | = Mobile: +1.484.477.8649 =20 =20 Collectively Creating a World of 3 Billion Maries | http://mifos.org = = = =20 =20 = = =20 =20 --=20 Ed Cable Director of Community Programs, Mifos Initiative edcable@mifos.org | Skype: edcable | Mobile: = +1.484.477.8649 =20 =20 Collectively Creating a World of 3 Billion Maries | http://mifos.org = =20 =20 =20 =20 =20 =20 =20 ------=_NextPart_000_0063_01D194BF.C5CCE920--