Zack and Robert have been working on contributing a fix to add the ability to reschedule a loan multiple times.
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. Ed On Mon, Apr 4, 2016 at 9:32 PM, Adi Raju <[email protected] <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWRpLnJhanUlNDBjb25mbHV4dGVjaG5vbG9naWVzLmNvbQ==> > wrote: > Hi Robert, > > > > 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’t > addressed those scenarios in further calculations. > > If the core code works for multi-reschedule, they wouldn’t have put this > check in the first place. > > > > 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? > > > > +Sander, who can help us with more clarifications on why such validations > were added. > > > > Regards, > > Adi > > > > > > *From:* robert wizglobal [mailto:[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBcm9iZXJ0JTQwd2l6Z2xvYmFsLmNvLmtl>] > > *Sent:* 04 April 2016 22:13 > *To:* Adi Raju <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWRpLnJhanUlNDBjb25mbHV4dGVjaG5vbG9naWVzLmNvbQ==> > > > *Cc:* Zack Wizglobal <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBemFjayU0MHdpemdsb2JhbC5jby5rZQ==>>; > Ed Cable <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBZWRjYWJsZSU0MG1pZm9zLm9yZw==>>; > [email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBcHJhbW9kJTQwY29uZmx1eHRlY2hub2xvZ2llcy5jb20=>; > Agris Varpins <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWdyaXMudmFycGlucyU0MG10Z2NhcGl0YWwuY2g=>>; > Andris Kaneps <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWthbmVwcyU0MG10Z2NhcGl0YWwuY2g=>>; > Philippe Storm <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBcHN0b3JtJTQwd2F0dWNyZWRpdC5jb20=> > > > *Subject:* Re: Mifos fix > > > > Hello Adi > > > > It Was Not a Major Fix Below is the Change i did on the > *LoanRescheduleRequestDataValidator > Class* > > > > /** > > * 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 > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRnd3dy5hcGFjaGUub3JnJTJGbGljZW5zZXMlMkZMSUNFTlNFLTIuMA==> > > * > > * 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; > > > > import java.lang.reflect.Type; > > import java.util.ArrayList; > > import java.util.List; > > import java.util.Map; > > > > 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.PlatformApiDataValidationException; > > import > org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; > > import org.apache.fineract.portfolio.loanaccount.domain.Loan; > > import > org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; > > import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; > > import > org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; > > import > org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; > > import > org.apache.fineract.portfolio.loanaccount.rescheduleloan.service.LoanRescheduleRequestReadPlatformService; > > import org.joda.time.LocalDate; > > import org.springframework.beans.factory.annotation.Autowired; > > import org.springframework.stereotype.Component; > > > > import com.google > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRmNvbS5nb29nbGU=> > .gson.JsonElement; > > import com.google > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRmNvbS5nb29nbGU=> > .gson.reflect.TypeToken; > > > > @Component > > public class LoanRescheduleRequestDataValidator { > > > > private final FromJsonHelper fromJsonHelper; > > private final LoanRescheduleRequestReadPlatformService > loanRescheduleRequestReadPlatformService; > > > > @Autowired > > public LoanRescheduleRequestDataValidator(FromJsonHelper > fromJsonHelper, > > LoanRescheduleRequestReadPlatformService > loanRescheduleRequestReadPlatformService) { > > this.fromJsonHelper = fromJsonHelper; > > this.loanRescheduleRequestReadPlatformService = > loanRescheduleRequestReadPlatformService; > > } > > > > /** > > * Validates the request to create a new loan reschedule entry > > * > > * @param jsonCommand > > * the JSON command object (instance of the JsonCommand > class) > > * @return void > > **/ > > public void validateForCreateAction(final JsonCommand jsonCommand, > final Loan loan) { > > > > final String jsonString = jsonCommand.json(); > > > > if (StringUtils.isBlank(jsonString)) { throw new > InvalidJsonException(); } > > > > final Type typeToken = new TypeToken<Map<String, Object>>() > {}.getType(); > > this.fromJsonHelper > > .checkForUnsupportedParameters(typeToken, jsonString, > RescheduleLoansApiConstants.CREATE_REQUEST_DATA_PARAMETERS); > > > > final List<ApiParameterError> dataValidationErrors = new > ArrayList<>(); > > final DataValidatorBuilder dataValidatorBuilder = new > DataValidatorBuilder(dataValidationErrors).resource(StringUtils > > .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); > > > > final JsonElement jsonElement = jsonCommand.parsedJson(); > > > > if (!loan.status().isActive()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", > "Loan is not active"); > > } > > > > final Long loanId = > this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.loanIdParamName, > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.loanIdParamName).value(loanId).notNull() > > .integerGreaterThanZero(); > > > > final LocalDate submittedOnDate = > this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.submittedOnDateParamName, > > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName).value(submittedOnDate).notNull(); > > > > if (submittedOnDate != null && > loan.getDisbursementDate().isAfter(submittedOnDate)) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.submittedOnDateParamName) > > .failWithCode("before.loan.disbursement.date", > "Submission date cannot be before the loan disbursement date"); > > } > > > > final LocalDate rescheduleFromDate = > this.fromJsonHelper.extractLocalDateNamed( > > RescheduleLoansApiConstants.rescheduleFromDateParamName, > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).value(rescheduleFromDate).notNull(); > > > > final Integer graceOnPrincipal = > this.fromJsonHelper.extractIntegerWithLocaleNamed( > > RescheduleLoansApiConstants.graceOnPrincipalParamName, > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).value(graceOnPrincipal) > > .ignoreIfNull().integerGreaterThanZero(); > > > > final Integer graceOnInterest = > this.fromJsonHelper.extractIntegerWithLocaleNamed( > > RescheduleLoansApiConstants.graceOnInterestParamName, > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnInterestParamName).value(graceOnInterest).ignoreIfNull() > > .integerGreaterThanZero(); > > > > final Integer extraTerms = > this.fromJsonHelper.extractIntegerWithLocaleNamed(RescheduleLoansApiConstants.extraTermsParamName, > > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.extraTermsParamName).value(extraTerms).ignoreIfNull() > > .integerGreaterThanZero(); > > > > final Long rescheduleReasonId = > this.fromJsonHelper.extractLongNamed(RescheduleLoansApiConstants.rescheduleReasonIdParamName, > > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonIdParamName).value(rescheduleReasonId).notNull() > > .integerGreaterThanZero(); > > > > final String rescheduleReasonComment = > this.fromJsonHelper.extractStringNamed( > > > RescheduleLoansApiConstants.rescheduleReasonCommentParamName, jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleReasonCommentParamName).value(rescheduleReasonComment) > > .ignoreIfNull().notExceedingLengthOf(500); > > > > final LocalDate adjustedDueDate = > this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.adjustedDueDateParamName, > > jsonElement); > > > > if (adjustedDueDate != null && rescheduleFromDate != null && > adjustedDueDate.isBefore(rescheduleFromDate)) { > > dataValidatorBuilder > > .reset() > > > .parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) > > > .failWithCode("adjustedDueDate.before.rescheduleFromDate", > > "Adjusted due date cannot be before the > reschedule from date"); > > } > > > > // at least one of the following must be provided => > graceOnPrincipal, > > // graceOnInterest, extraTerms, newInterestRate > > if > (!this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnPrincipalParamName, > jsonElement) > > && > !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.graceOnInterestParamName, > jsonElement) > > && > !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.extraTermsParamName, > jsonElement) > > && > !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.newInterestRateParamName, > jsonElement) > > && > !this.fromJsonHelper.parameterExists(RescheduleLoansApiConstants.adjustedDueDateParamName, > jsonElement)) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.graceOnPrincipalParamName).notNull(); > > } > > > > if (rescheduleFromDate != null) { > > LoanRepaymentScheduleInstallment installment = > loan.getRepaymentScheduleInstallment(rescheduleFromDate); > > > > if (installment == null) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) > > > .failWithCode("repayment.schedule.installment.does.not.exist", "Repayment > schedule installment does not exist"); > > } > > /* > > if (installment != null && installment.isObligationsMet()) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) > > > .failWithCode("repayment.schedule.installment.obligation.met", "Repayment > schedule installment obligation met"); > > } */ > > /* > > if (installment != null && installment.isPartlyPaid()) { > > > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName) > > > .failWithCode("repayment.schedule.installment.partly.paid", "Repayment > schedule installment is partly paid"); > > } */ > > } > > > > if (loanId != null) { > > List<LoanRescheduleRequestData> loanRescheduleRequestData = > this.loanRescheduleRequestReadPlatformService > > .readLoanRescheduleRequests(loanId, > LoanStatus.APPROVED.getValue()); > > /* //commented this for loan reshedule > > if (loanRescheduleRequestData.size() > 0) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled", > > "The loan can only be rescheduled once."); > > } */ > > } > > if(loan.isMultiDisburmentLoan()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleForMultiDisbursementNotSupportedErrorCode, > > "Loan rescheduling is not supported for > multidisbursement loans"); > > } > > > > if(loan.isInterestRecalculationEnabledForProduct()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode(RescheduleLoansApiConstants.resheduleWithInterestRecalculationNotSupportedErrorCode, > > "Loan rescheduling is not supported for the loan > product with interest recalculation enabled"); > > } > > > > if (!dataValidationErrors.isEmpty()) { throw new > PlatformApiDataValidationException(dataValidationErrors); } > > } > > > > /** > > * Validates a user request to approve a loan reschedule request > > * > > * @param jsonCommand > > * the JSON command object (instance of the JsonCommand > class) > > * @return void > > **/ > > public void validateForApproveAction(final JsonCommand jsonCommand, > LoanRescheduleRequest loanRescheduleRequest) { > > final String jsonString = jsonCommand.json(); > > > > if (StringUtils.isBlank(jsonString)) { throw new > InvalidJsonException(); } > > > > final Type typeToken = new TypeToken<Map<String, Object>>() > {}.getType(); > > this.fromJsonHelper.checkForUnsupportedParameters(typeToken, > jsonString, > > > RescheduleLoansApiConstants.APPROVE_REQUEST_DATA_PARAMETERS); > > > > final List<ApiParameterError> dataValidationErrors = new > ArrayList<>(); > > final DataValidatorBuilder dataValidatorBuilder = new > DataValidatorBuilder(dataValidationErrors).resource(StringUtils > > .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); > > > > final JsonElement jsonElement = jsonCommand.parsedJson(); > > > > final LocalDate approvedOnDate = > this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.approvedOnDateParam, > > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam).value(approvedOnDate).notNull(); > > > > if (approvedOnDate != null && > loanRescheduleRequest.getSubmittedOnDate().isAfter(approvedOnDate)) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.approvedOnDateParam) > > .failWithCode("before.submission.date", "Approval date > cannot be before the request submission date."); > > } > > > > LoanRescheduleRequestStatusEnumData > loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations > > .status(loanRescheduleRequest.getStatusEnum()); > > > > if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( > > "request.is.not.in > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRnJlcXVlc3QuaXMubm90Lmlu> > .submitted.and.pending.state", > > "Loan reschedule request approval is not allowed. " > > + "Loan reschedule request is not in submitted > and pending approval state."); > > } > > > > LocalDate rescheduleFromDate = > loanRescheduleRequest.getRescheduleFromDate(); > > final Loan loan = loanRescheduleRequest.getLoan(); > > > > if (loan != null) { > > Long loanId = loan.getId(); > > > > if (!loan.status().isActive()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.is.not.active", > "Loan is not active"); > > } > > > > if (rescheduleFromDate != null) { > > LoanRepaymentScheduleInstallment installment = > loan.getRepaymentScheduleInstallment(rescheduleFromDate); > > > > if (installment == null) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( > > > "loan.repayment.schedule.installment.does.not.exist", "Repayment schedule > installment does not exist"); > > } > > /* > > if (installment != null && installment.isObligationsMet()) > { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( > > "loan.repayment.schedule.installment." + > "obligation.met", "Repayment schedule installment obligation met"); > > } */ > > } > > > > if (loanId != null) { > > List<LoanRescheduleRequestData> loanRescheduleRequestData > = this.loanRescheduleRequestReadPlatformService > > .readLoanRescheduleRequests(loanId, > LoanStatus.APPROVED.getValue()); > > /* > > if (loanRescheduleRequestData.size() > 0) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode("loan.already.rescheduled", > > "The loan can only be rescheduled once."); > > } */ > > } > > } > > > > if (!dataValidationErrors.isEmpty()) { throw new > PlatformApiDataValidationException(dataValidationErrors); } > > } > > > > /** > > * Validates a user request to reject a loan reschedule request > > * > > * @param jsonCommand > > * the JSON command object (instance of the JsonCommand > class) > > * @return void > > **/ > > public void validateForRejectAction(final JsonCommand jsonCommand, > LoanRescheduleRequest loanRescheduleRequest) { > > final String jsonString = jsonCommand.json(); > > > > if (StringUtils.isBlank(jsonString)) { throw new > InvalidJsonException(); } > > > > final Type typeToken = new TypeToken<Map<String, Object>>() > {}.getType(); > > this.fromJsonHelper > > .checkForUnsupportedParameters(typeToken, jsonString, > RescheduleLoansApiConstants.REJECT_REQUEST_DATA_PARAMETERS); > > > > final List<ApiParameterError> dataValidationErrors = new > ArrayList<>(); > > final DataValidatorBuilder dataValidatorBuilder = new > DataValidatorBuilder(dataValidationErrors).resource(StringUtils > > .lowerCase(RescheduleLoansApiConstants.ENTITY_NAME)); > > > > final JsonElement jsonElement = jsonCommand.parsedJson(); > > > > final LocalDate rejectedOnDate = > this.fromJsonHelper.extractLocalDateNamed(RescheduleLoansApiConstants.rejectedOnDateParam, > > jsonElement); > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam).value(rejectedOnDate).notNull(); > > > > if (rejectedOnDate != null && > loanRescheduleRequest.getSubmittedOnDate().isAfter(rejectedOnDate)) { > > > dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rejectedOnDateParam) > > .failWithCode("before.submission.date", "Rejection > date cannot be before the request submission date."); > > } > > > > LoanRescheduleRequestStatusEnumData > loanRescheduleRequestStatusEnumData = LoanRescheduleRequestEnumerations > > .status(loanRescheduleRequest.getStatusEnum()); > > > > if (!loanRescheduleRequestStatusEnumData.isPendingApproval()) { > > > dataValidatorBuilder.reset().failWithCodeNoParameterAddedToErrorCode( > > "request.is.not.in > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRnJlcXVlc3QuaXMubm90Lmlu> > .submitted.and.pending.state", > > "Loan reschedule request rejection is not allowed. " > > + "Loan reschedule request is not in submitted > and pending approval state."); > > } > > > > if (!dataValidationErrors.isEmpty()) { throw new > PlatformApiDataValidationException(dataValidationErrors); } > > } > > } > > > > > > On Sat, Apr 2, 2016 at 5:14 AM, Adi Raju <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWRpLnJhanUlNDBjb25mbHV4dGVjaG5vbG9naWVzLmNvbQ==>> > 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" <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBemFjayU0MHdpemdsb2JhbC5jby5rZQ==>> > wrote: > > Hi Ed, > > > > 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. > > > > -- > Kind Regards, > > Zack Githinji > Systems Developer > Wizglobal Kenya > P.O. BOX 21373-00100 > Nairobi. > Mobile: +254 (0) 722 649199 > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=dGVsJTNBJTI1MkIyNTQlMjUyMCUyNTI4MCUyNTI5JTI1MjA3MjIlMjUyMDY0OTE5OQ==> > > [email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBemFjayU0MHdpemdsb2JhbC5jby5rZQ==> > www.wizglobal.co.ke > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRnd3dy53aXpnbG9iYWwuY28ua2U=> > > > > On 24 Mar 2016, at 21:06, Andris Kaneps <[email protected] > <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBYWthbmVwcyU0MG10Z2NhcGl0YWwuY2g=>> > wrote: > > > > Dear Zack, Ed, Adi and Pramod, > > 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. > > 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. > > As discussed with Ed, we would like to contribute the fix to Mifos > community so that the problem is solved in next Mifos update. > > So Adi and Pramod, could you please get in touch directly with Zack to > discuss all the technical details? > > Kind regards, > > Andris Kaneps > > > > > > > > > -- *Ed Cable* Director of Community Programs, Mifos Initiative [email protected] <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=bWFpbHRvJTNBZWRjYWJsZSU0MG1pZm9zLm9yZw==> | Skype: edcable | Mobile: +1.484.477.8649 *Collectively Creating a World of 3 Billion Maries | *http://mifos.org <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRm1pZm9zLm9yZw==> <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRmZhY2Vib29rLmNvbSUyRm1pZm9z> <https://web.chilipiper.com/link/mifos.org/57053b41e4b02bbec5bc216d?link=aHR0cCUzQSUyRiUyRnd3dy50d2l0dGVyLmNvbSUyRm1pZm9z>
