Repository: incubator-fineract Updated Branches: refs/heads/develop 11cd6ef33 -> 961aa3df8
http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java index 7d4a8f4..8f871b0 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationCommandFromApiJsonHelper.java @@ -91,7 +91,8 @@ public final class LoanApplicationCommandFromApiJsonHelper { LoanApiConstants.syncDisbursementWithMeetingParameterName,// optional LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, - LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName)); + LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName, + LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose)); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; @@ -469,6 +470,18 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance) .ignoreIfNull().positiveAmount(); } + + if(loanProduct.canUseForTopup()){ + if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isTopup, element)){ + final Boolean isTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element); + baseDataValidator.reset().parameter(LoanApiConstants.isTopup).value(isTopup).validateForBooleanValue(); + + if(isTopup != null && isTopup){ + final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element); + baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero(); + } + } + } validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct); if (!dataValidationErrors.isEmpty()) { throw new PlatformApiDataValidationException(dataValidationErrors); } @@ -899,6 +912,19 @@ public final class LoanApplicationCommandFromApiJsonHelper { baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance) .ignoreIfNull().positiveAmount(); } + + if(loanProduct.canUseForTopup()){ + if(this.fromApiJsonHelper.parameterExists(LoanApiConstants.isTopup, element)){ + final Boolean isTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element); + baseDataValidator.reset().parameter(LoanApiConstants.isTopup).value(isTopup).ignoreIfNull().validateForBooleanValue(); + + if(isTopup != null && isTopup){ + final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element); + baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero(); + } + } + } + validateLoanMultiDisbursementdate(element, baseDataValidator, expectedDisbursementDate, principal); validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java index 8432273..4344455 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanApplicationWritePlatformServiceJpaRepositoryImpl.java @@ -40,6 +40,8 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; +import org.apache.fineract.infrastructure.core.exceptionmapper.PlatformDomainRuleExceptionMapper; import org.apache.fineract.infrastructure.entityaccess.exception.NotOfficeSpecificProductException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; @@ -83,17 +85,7 @@ import org.apache.fineract.portfolio.group.exception.GroupNotActiveException; import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants; import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData; import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO; -import org.apache.fineract.portfolio.loanaccount.domain.DefaultLoanLifecycleStateMachine; -import org.apache.fineract.portfolio.loanaccount.domain.Loan; -import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; -import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails; -import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory; -import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; -import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; -import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper; +import org.apache.fineract.portfolio.loanaccount.domain.*; import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException; import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted; import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified; @@ -287,6 +279,43 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(), newLoanApplication); + if(loanProduct.canUseForTopup() && clientId != null){ + final Boolean isTopup = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isTopup); + if(null == isTopup){ + newLoanApplication.setIsTopup(false); + }else{ + newLoanApplication.setIsTopup(isTopup); + } + + if(newLoanApplication.isTopup()){ + final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose); + final Loan loanToClose = this.loanRepository.findNonClosedLoanThatBelongsToClient(loanIdToClose, clientId); + if(loanToClose == null){ + throw new LoanNotFoundException(loanIdToClose); + } + if(loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported", + "Topup on loan with multi-tranche disbursal and without interest recalculation is not supported."); + } + final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate(); + if(!newLoanApplication.getSubmittedOnDate().isAfter(disbursalDateOfLoanToClose)){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.submitted.date.before.topup.loan.disbursal.date", + "Submitted date of this loan application "+newLoanApplication.getSubmittedOnDate() + +" is before the disbursed date of loan to be closed "+ disbursalDateOfLoanToClose); + } + BigDecimal loanOutstanding = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanIdToClose, + newLoanApplication.getDisbursementDate()).getAmount(); + final BigDecimal firstDisbursalAmount = newLoanApplication.getFirstDisbursalAmount(); + if(loanOutstanding.compareTo(firstDisbursalAmount) > 0){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", + "Topup loan amount should be greater than outstanding amount of loan to be closed."); + } + + final LoanTopupDetails topupDetails = new LoanTopupDetails(newLoanApplication, loanIdToClose); + newLoanApplication.setTopupLoanDetails(topupDetails); + } + } + this.loanRepository.save(newLoanApplication); if (loanProduct.isInterestRecalculationEnabled()) { @@ -555,6 +584,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final Set<LoanCollateral> possiblyModifedLoanCollateralItems = this.loanCollateralAssembler .fromParsedJson(command.parsedJson()); + final Map<String, Object> changes = existingLoanApplication.loanApplicationModification(command, possiblyModifedLoanCharges, possiblyModifedLoanCollateralItems, this.aprCalculator, isChargeModified); @@ -620,6 +650,56 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa updateProductRelatedDetails(productRelatedDetail, existingLoanApplication); } + if(existingLoanApplication.getLoanProduct().canUseForTopup() && existingLoanApplication.getClientId() != null){ + final Boolean isTopup = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isTopup); + if(command.isChangeInBooleanParameterNamed(LoanApiConstants.isTopup, existingLoanApplication.isTopup())){ + existingLoanApplication.setIsTopup(isTopup); + changes.put(LoanApiConstants.isTopup, isTopup); + } + + if(existingLoanApplication.isTopup()){ + final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose); + LoanTopupDetails existingLoanTopupDetails = existingLoanApplication.getTopupLoanDetails(); + if(existingLoanTopupDetails == null + || (existingLoanTopupDetails != null && existingLoanTopupDetails.getLoanIdToClose() != loanIdToClose)){ + final Loan loanToClose = this.loanRepository.findNonClosedLoanThatBelongsToClient(loanIdToClose, existingLoanApplication.getClientId()); + if(loanToClose == null){ + throw new LoanNotFoundException(loanIdToClose); + } + if(loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported", + "Topup on loan with multi-tranche disbursal and without interest recalculation is not supported."); + } + final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate(); + if(!existingLoanApplication.getSubmittedOnDate().isAfter(disbursalDateOfLoanToClose)){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.submitted.date.before.topup.loan.disbursal.date", + "Submitted date of this loan application "+existingLoanApplication.getSubmittedOnDate() + +" is before the disbursed date of loan to be closed "+ disbursalDateOfLoanToClose); + } + BigDecimal loanOutstanding = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanIdToClose, + existingLoanApplication.getDisbursementDate()).getAmount(); + final BigDecimal firstDisbursalAmount = existingLoanApplication.getFirstDisbursalAmount(); + if(loanOutstanding.compareTo(firstDisbursalAmount) > 0){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", + "Topup loan amount should be greater than outstanding amount of loan to be closed."); + } + + final LoanTopupDetails topupDetails = new LoanTopupDetails(existingLoanApplication, loanIdToClose); + existingLoanApplication.setTopupLoanDetails(topupDetails); + changes.put(LoanApiConstants.loanIdToClose, loanIdToClose); + } + }else{ + existingLoanApplication.setTopupLoanDetails(null); + } + } else { + if(existingLoanApplication.isTopup()){ + existingLoanApplication.setIsTopup(false); + existingLoanApplication.setTopupLoanDetails(null); + changes.put(LoanApiConstants.isTopup, false); + } + } + + final String fundIdParamName = "fundId"; if (changes.containsKey(fundIdParamName)) { final Long fundId = command.longValueOfParameterNamed(fundIdParamName); @@ -981,6 +1061,22 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); } + if(loan.isTopup() && loan.getClientId() != null){ + final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); + final Loan loanToClose = this.loanRepository.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); + if(loanToClose == null){ + throw new LoanNotFoundException(loanIdToClose); + } + + BigDecimal loanOutstanding = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanIdToClose, + expectedDisbursementDate).getAmount(); + final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); + if(loanOutstanding.compareTo(firstDisbursalAmount) > 0){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", + "Topup loan amount should be greater than outstanding amount of loan to be closed."); + } + } + saveAndFlushLoanWithDataIntegrityViolationChecks(loan); final String noteText = command.stringValueOfParameterNamed("note"); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java index 7283ca0..a99064a 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java @@ -52,7 +52,9 @@ import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.organisation.staff.data.StaffData; import org.apache.fineract.organisation.staff.service.StaffReadPlatformService; import org.apache.fineract.portfolio.account.data.AccountTransferData; +import org.apache.fineract.portfolio.accountdetails.data.LoanAccountSummaryData; import org.apache.fineract.portfolio.accountdetails.domain.AccountType; +import org.apache.fineract.portfolio.accountdetails.service.AccountDetailsReadPlatformService; import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations; import org.apache.fineract.portfolio.calendar.data.CalendarData; import org.apache.fineract.portfolio.calendar.domain.CalendarEntityType; @@ -153,6 +155,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { private final FloatingRatesReadPlatformService floatingRatesReadPlatformService; private final LoanUtilService loanUtilService; private final ConfigurationDomainService configurationDomainService; + private final AccountDetailsReadPlatformService accountDetailsReadPlatformService; @Autowired public LoanReadPlatformServiceImpl(final PlatformSecurityContext context, final LoanRepository loanRepository, @@ -166,7 +169,8 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { final PaymentTypeReadPlatformService paymentTypeReadPlatformService, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, final FloatingRatesReadPlatformService floatingRatesReadPlatformService, final LoanUtilService loanUtilService, - final ConfigurationDomainService configurationDomainService) { + final ConfigurationDomainService configurationDomainService, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService) { this.context = context; this.loanRepository = loanRepository; this.loanTransactionRepository = loanTransactionRepository; @@ -187,6 +191,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { this.floatingRatesReadPlatformService = floatingRatesReadPlatformService; this.loanUtilService = loanUtilService; this.configurationDomainService = configurationDomainService; + this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; } @Override @@ -618,7 +623,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " l.is_floating_interest_rate as isFloatingInterestRate, " + " l.interest_rate_differential as interestRateDifferential, " + " l.create_standing_instruction_at_disbursement as createStandingInstructionAtDisbursement, " - + " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap " + + " lpvi.minimum_gap as minimuminstallmentgap, lpvi.maximum_gap as maximuminstallmentgap, " + + " lp.can_use_for_topup as canUseForTopup, " + + " l.is_topup as isTopup, " + + " topup.closure_loan_id as closureLoanId, " + + " topuploan.account_no as closureLoanAccountNo, " + + " topup.topup_amount as topupAmount " + " from m_loan l" // + " join m_product_loan lp on lp.id = l.product_id" // + " left join m_loan_recalculation_details lir on lir.loan_id = l.id " @@ -637,7 +647,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { + " left join m_code_value cv on cv.id = l.loanpurpose_cv_id" + " left join m_code_value codev on codev.id = l.writeoff_reason_cv_id" + " left join ref_loan_transaction_processing_strategy lps on lps.id = l.loan_transaction_strategy_id" - + " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id"; + + " left join m_product_loan_variable_installment_config lpvi on lpvi.loan_product_id = l.product_id" + + " left join m_loan_topup as topup on l.id = topup.loan_id" + + " left join m_loan as topuploan on topuploan.id = topup.closure_loan_id"; } @@ -931,6 +943,12 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { isCompoundingToBePostedAsTransaction, allowCompoundingOnEod); } + final boolean canUseForTopup = rs.getBoolean("canUseForTopup"); + final boolean isTopup = rs.getBoolean("isTopup"); + final Long closureLoanId = rs.getLong("closureLoanId"); + final String closureLoanAccountNo = rs.getString("closureLoanAccountNo"); + final BigDecimal topupAmount = rs.getBigDecimal("topupAmount"); + return LoanAccountData.basicLoanDetails(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, groupData, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, @@ -944,7 +962,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { loanProductCounter, multiDisburseLoan, canDefineInstallmentAmount, fixedEmiAmount, outstandingLoanBalance, inArrears, graceOnArrearsAgeing, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, createStandingInstructionAtDisbursement, isvariableInstallmentsAllowed, minimumGap, - maximumGap, loanSubStatus); + maximumGap, loanSubStatus, canUseForTopup, isTopup, closureLoanId, closureLoanAccountNo, topupAmount); } } @@ -1049,9 +1067,9 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { if (!this.disbursement.isDisbursed()) { excludePastUndisbursed = false; } - for(DisbursementData disbursementData : disbursementData){ - if(disbursementData.getChargeAmount() != null){ - disbursementChargeAmount = disbursementChargeAmount.subtract(disbursementData.getChargeAmount()); + for(DisbursementData data : disbursementData){ + if(data.getChargeAmount() != null){ + disbursementChargeAmount = disbursementChargeAmount.subtract(data.getChargeAmount()); } } this.outstandingLoanPrincipalBalance = BigDecimal.ZERO; @@ -1425,10 +1443,15 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService { } } + Collection<LoanAccountSummaryData> clientActiveLoanOptions = null; + if(loanProduct.canUseForTopup() && clientId != null){ + clientActiveLoanOptions = this.accountDetailsReadPlatformService.retrieveClientActiveLoanAccountSummary(clientId); + } + return LoanAccountData.loanProductWithTemplateDefaults(loanProduct, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDaysOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, - fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter); + fundOptions, chargeOptions, loanPurposeOptions, loanCollateralOptions, loanCycleCounter, clientActiveLoanOptions); } @Override http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index e3f6ccb..b879406 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -41,6 +41,7 @@ import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; @@ -142,16 +143,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementC import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType; -import org.apache.fineract.portfolio.loanaccount.exception.DateMismatchException; -import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException; -import org.apache.fineract.portfolio.loanaccount.exception.InvalidPaidInAdvanceAmountException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanDisbursalException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanForeclosureException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanMultiDisbursementException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerAssignmentException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanOfficerUnassignmentException; -import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionNotFoundException; -import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException; +import org.apache.fineract.portfolio.loanaccount.exception.*; import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.OverdueLoanScheduleData; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator; @@ -362,16 +354,37 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf ChangedTransactionDetail changedTransactionDetail = null; if (canDisburse) { Money disburseAmount = loan.adjustDisburseAmount(command, actualDisbursementDate); + Money amountToDisburse = disburseAmount.copy(); boolean recalculateSchedule = amountBeforeAdjust.isNotEqualTo(loan.getPrincpal()); final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); + + if(loan.isTopup() && loan.getClientId() != null){ + final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose(); + final Loan loanToClose = this.loanRepository.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId()); + if(loanToClose == null){ + throw new LoanNotFoundException(loanIdToClose); + } + BigDecimal loanOutstanding = this.loanReadPlatformService.retrieveLoanPrePaymentTemplate(loanIdToClose, + actualDisbursementDate).getAmount(); + final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); + if(loanOutstanding.compareTo(firstDisbursalAmount) > 0){ + throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", + "Topup loan amount should be greater than outstanding amount of loan to be closed."); + } + + amountToDisburse = disburseAmount.minus(loanOutstanding); + + disburseLoanToLoan(loan, command, loanOutstanding); + } + if (isAccountTransfer) { - disburseLoanToSavings(loan, command, disburseAmount, paymentDetail); + disburseLoanToSavings(loan, command, amountToDisburse, paymentDetail); existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); } else { existingTransactionIds.addAll(loan.findExistingTransactionIds()); existingReversedTransactionIds.addAll(loan.findExistingReversedTransactionIds()); - LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), disburseAmount, paymentDetail, + LoanTransaction disbursementTransaction = LoanTransaction.disbursement(loan.getOffice(), amountToDisburse, paymentDetail, actualDisbursementDate, txnExternalId, DateUtils.getLocalDateTimeOfTenant(), currentUser); disbursementTransaction.updateLoan(loan); loan.addLoanTransaction(disbursementTransaction); @@ -1685,6 +1698,22 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf .withSavingsId(portfolioAccountData.accountId()).build(); } + public void disburseLoanToLoan(final Loan loan, final JsonCommand command, final BigDecimal amount) { + + final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); + final String txnExternalId = command.stringValueOfParameterNamedAllowingNull("externalId"); + + final Locale locale = command.extractLocale(); + final DateTimeFormatter fmt = DateTimeFormat.forPattern(command.dateFormat()).withLocale(locale); + final AccountTransferDTO accountTransferDTO = new AccountTransferDTO(transactionDate, amount, + PortfolioAccountType.LOAN, PortfolioAccountType.LOAN, loan.getId(), loan.getTopupLoanDetails().getLoanIdToClose(), + "Loan Topup", locale, fmt, LoanTransactionType.DISBURSEMENT.getValue(), LoanTransactionType.REPAYMENT.getValue(), + txnExternalId, loan, null); + AccountTransferDetails accountTransferDetails = this.accountTransfersWritePlatformService.repayLoanWithTopup(accountTransferDTO); + loan.getTopupLoanDetails().setAccountTransferDetails(accountTransferDetails.getId()); + loan.getTopupLoanDetails().setTopupAmount(amount); + } + public void disburseLoanToSavings(final Loan loan, final JsonCommand command, final Money amount, final PaymentDetail paymentDetail) { final LocalDate transactionDate = command.localDateValueOfParameterNamed("actualDisbursementDate"); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java index f704925..5022d01 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/LoanProductConstants.java @@ -129,6 +129,8 @@ public interface LoanProductConstants { public static final String allowPartialPeriodInterestCalcualtionParamName = "allowPartialPeriodInterestCalcualtion"; + + public static final String canUseForTopup = "canUseForTopup"; } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java index a269d54..80f1c3e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java @@ -91,7 +91,7 @@ public class LoanProductsApiResource { "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate", - "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed")); + "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup)); private final Set<String> PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java index 68d1724..bf05d4e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java @@ -126,6 +126,7 @@ public class LoanProductData { private final Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariationsForBorrowerCycle; // accounting private final EnumOptionData accountingRule; + private final boolean canUseForTopup; private Map<String, Object> accountingMappings; private Collection<PaymentTypeToGLAccountMapper> paymentChannelToFundSourceMappings; private Collection<ChargeToGLAccountMapper> feeToIncomeAccountMappings; @@ -257,6 +258,7 @@ public class LoanProductData { final Integer installmentAmountInMultiplesOf = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final boolean syncExpectedWithDisbursementDate = false; + final boolean canUseForTopup = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -272,7 +274,7 @@ public class LoanProductData { loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate); + syncExpectedWithDisbursementDate, canUseForTopup); } @@ -348,6 +350,7 @@ public class LoanProductData { final Integer installmentAmountInMultiplesOf = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final boolean syncExpectedWithDisbursementDate = false; + final boolean canUseForTopup = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -363,7 +366,7 @@ public class LoanProductData { loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate); + syncExpectedWithDisbursementDate, canUseForTopup); } @@ -446,6 +449,7 @@ public class LoanProductData { final Integer installmentAmountInMultiplesOf = null; final LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; final boolean syncExpectedWithDisbursementDate = false; + final boolean canUseForTopup = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -461,9 +465,8 @@ public class LoanProductData { installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate); + syncExpectedWithDisbursementDate, canUseForTopup); - } public static LoanProductData withAccountingDetails(final LoanProductData productData, final Map<String, Object> accountingMappings, @@ -486,14 +489,13 @@ public class LoanProductData { final EnumOptionData interestCalculationPeriodType, final Boolean allowPartialPeriodInterestCalcualtion, final Long fundId, final String fundName, final Long transactionProcessingStrategyId, final String transactionProcessingStrategyName, final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, - final Collection<ChargeData> charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle, - boolean useBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String status, final String externalId, + final Collection<ChargeData> charges, final EnumOptionData accountingType, final boolean includeInBorrowerCycle, boolean useBorrowerCycle, final LocalDate startDate, + final LocalDate closeDate, final String status, final String externalId, Collection<LoanProductBorrowerCycleVariationData> principalVariations, Collection<LoanProductBorrowerCycleVariationData> interestRateVariations, Collection<LoanProductBorrowerCycleVariationData> numberOfRepaymentVariations, Boolean multiDisburseLoan, - Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, - final Integer overdueDaysForNPA, final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, - final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData, + Integer maxTrancheCount, BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, final Integer overdueDaysForNPA, + final EnumOptionData daysInMonthType, final EnumOptionData daysInYearType, final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationData interestRecalculationData, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, boolean holdGuaranteeFunds, final LoanProductGuaranteeData loanProductGuaranteeData, final BigDecimal principalThresholdForLastInstallment, final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion, boolean canDefineInstallmentAmount, @@ -502,7 +504,7 @@ public class LoanProductData { BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, - final boolean syncExpectedWithDisbursementDate) { + final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup) { this.id = id; this.name = name; this.shortName = shortName; @@ -605,6 +607,7 @@ public class LoanProductData { this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.preClosureInterestCalculationStrategyOptions = null; this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate; + this.canUseForTopup = canUseForTopup; } @@ -740,6 +743,7 @@ public class LoanProductData { this.installmentAmountInMultiplesOf = productData.installmentAmountInMultiplesOf; this.preClosureInterestCalculationStrategyOptions = preCloseInterestCalculationStrategyOptions; this.syncExpectedWithDisbursementDate = productData.syncExpectedWithDisbursementDate; + this.canUseForTopup = productData.canUseForTopup; } private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> charges) { @@ -1093,4 +1097,7 @@ public class LoanProductData { return syncExpectedWithDisbursementDate; } + public boolean canUseForTopup() { + return this.canUseForTopup; + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java index 2cdb0d1..f461b17 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProduct.java @@ -179,6 +179,10 @@ public class LoanProduct extends AbstractPersistable<Long> { @Column(name = "sync_expected_with_disbursement_date") private boolean syncExpectedWithDisbursementDate; + + @Column(name = "can_use_for_topup", nullable = false) + private boolean canUseForTopup = false; + public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) { @@ -323,6 +327,11 @@ public class LoanProduct extends AbstractPersistable<Long> { final boolean syncExpectedWithDisbursementDate = command.booleanPrimitiveValueOfParameterNamed("syncExpectedWithDisbursementDate"); + + final boolean canUseForTopup = command.parameterExists(LoanProductConstants.canUseForTopup) + ? command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canUseForTopup) + : false; + return new LoanProduct(fund, loanTransactionProcessingStrategy, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, interestFrequencyType, annualInterestRate, interestMethod, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, repaymentEvery, @@ -336,7 +345,7 @@ public class LoanProduct extends AbstractPersistable<Long> { installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments, - maximumGapBetweenInstallments, syncExpectedWithDisbursementDate); + maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup); } @@ -552,12 +561,10 @@ public class LoanProduct extends AbstractPersistable<Long> { final Integer repayEvery, final PeriodFrequencyType repaymentFrequencyType, final Integer defaultNumberOfInstallments, final Integer defaultMinNumberOfInstallments, final Integer defaultMaxNumberOfInstallments, final Integer graceOnPrincipalPayment, final Integer recurringMoratoriumOnPrincipalPeriods, final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, - final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges, - final AccountingRuleType accountingRuleType, final boolean includeInBorrowerCycle, final LocalDate startDate, - final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle, - final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, final boolean multiDisburseLoan, - final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, final Integer graceOnArrearsAgeing, - final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, + final AmortizationMethod amortizationMethod, final BigDecimal inArrearsTolerance, final List<Charge> charges, final AccountingRuleType accountingRuleType, + final boolean includeInBorrowerCycle, final LocalDate startDate, final LocalDate closeDate, final String externalId, final boolean useBorrowerCycle, + final Set<LoanProductBorrowerCycleVariations> loanProductBorrowerCycleVariations, final boolean multiDisburseLoan, final Integer maxTrancheCount, final BigDecimal outstandingLoanBalance, + final Integer graceOnArrearsAgeing, final Integer overdueDaysForNPA, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, final boolean isInterestRecalculationEnabled, final LoanProductInterestRecalculationDetails productInterestRecalculationDetails, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final boolean holdGuarantorFunds, @@ -568,7 +575,7 @@ public class LoanProduct extends AbstractPersistable<Long> { BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, - final boolean syncExpectedWithDisbursementDate) { + final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -644,6 +651,7 @@ public class LoanProduct extends AbstractPersistable<Long> { this.installmentAmountInMultiplesOf = installmentAmountInMultiplesOf; this.syncExpectedWithDisbursementDate = syncExpectedWithDisbursementDate; + this.canUseForTopup = canUseForTopup; } public MonetaryCurrency getCurrency() { @@ -1023,6 +1031,12 @@ public class LoanProduct extends AbstractPersistable<Long> { this.installmentAmountInMultiplesOf = newValue; } + if (command.isChangeInBooleanParameterNamed(LoanProductConstants.canUseForTopup, this.canUseForTopup)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.canUseForTopup); + actualChanges.put(LoanProductConstants.canUseForTopup, newValue); + this.canUseForTopup = newValue; + } + return actualChanges; } @@ -1347,4 +1361,8 @@ public class LoanProduct extends AbstractPersistable<Long> { return this.allowVariabeInstallments; } + public boolean canUseForTopup(){ + return this.canUseForTopup; + } + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java index f9c673b..e0a3f2e 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java @@ -107,7 +107,8 @@ public final class LoanProductDataValidator { LoanProductConstants.recalculationCompoundingFrequencyOnDayParamName, LoanProductConstants.recalculationRestFrequencyWeekdayParamName, LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName, - LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName)); + LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName, + LoanProductConstants.canUseForTopup)); private final FromJsonHelper fromApiJsonHelper; @@ -621,6 +622,12 @@ public final class LoanProductDataValidator { validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, null); + if(this.fromApiJsonHelper.parameterExists(LoanProductConstants.canUseForTopup, element)){ + final Boolean canUseForTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.canUseForTopup, + element); + baseDataValidator.reset().parameter(LoanProductConstants.canUseForTopup).value(canUseForTopup).validateForBooleanValue(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } @@ -1440,6 +1447,12 @@ public final class LoanProductDataValidator { validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct); + if(this.fromApiJsonHelper.parameterExists(LoanProductConstants.canUseForTopup, element)){ + final Boolean canUseForTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.canUseForTopup, + element); + baseDataValidator.reset().parameter(LoanProductConstants.canUseForTopup).value(canUseForTopup).validateForBooleanValue(); + } + throwExceptionIfValidationWarningsExist(dataValidationErrors); } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java index e3a511d..00a3c59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductReadPlatformServiceImpl.java @@ -224,7 +224,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo + "lfr.is_floating_interest_rate_calculation_allowed as isFloatingInterestRateCalculationAllowed, " + "lp.allow_variabe_installments as isVariableIntallmentsAllowed, " + "lvi.minimum_gap as minimumGap, " - + "lvi.maximum_gap as maximumGap " + + "lvi.maximum_gap as maximumGap, " + + "lp.can_use_for_topup as canUseForTopup " + " 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 " @@ -444,6 +445,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final boolean accountMovesOutOfNPAOnlyOnArrearsCompletion = rs.getBoolean("accountMovesOutOfNPAOnlyOnArrearsCompletion"); final boolean syncExpectedWithDisbursementDate = rs.getBoolean("syncExpectedWithDisbursementDate"); + final boolean canUseForTopup = rs.getBoolean("canUseForTopup"); + return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, @@ -459,7 +462,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap, - maximumGap, syncExpectedWithDisbursementDate); + maximumGap, syncExpectedWithDisbursementDate, canUseForTopup); } } http://git-wip-us.apache.org/repos/asf/incubator-fineract/blob/961aa3df/fineract-provider/src/main/resources/sql/migrations/core_db/V318__topuploan.sql ---------------------------------------------------------------------- diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V318__topuploan.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V318__topuploan.sql new file mode 100644 index 0000000..d89d771 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V318__topuploan.sql @@ -0,0 +1,40 @@ +-- +-- 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 +-- +-- 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. +-- + +ALTER TABLE `m_product_loan` + ADD COLUMN `can_use_for_topup` TINYINT(1) NOT NULL DEFAULT 0 AFTER `instalment_amount_in_multiples_of`; + +ALTER TABLE `m_loan` + ADD COLUMN `is_topup` TINYINT(1) NOT NULL DEFAULT 0 AFTER `loan_sub_status_id`; + +CREATE TABLE `m_loan_topup` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `loan_id` BIGINT NOT NULL, + `closure_loan_id` BIGINT NOT NULL, + `account_transfer_details_id` BIGINT NULL, + `topup_amount` DECIMAL(19,6) NULL DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `m_loan_topup_FK_loan_id` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`), + CONSTRAINT `m_loan_topup_FK_closure_loan_id` FOREIGN KEY (`closure_loan_id`) REFERENCES `m_loan` (`id`), + CONSTRAINT `m_loan_topup_FK_account_transfer_details_id` FOREIGN KEY (`account_transfer_details_id`) REFERENCES `m_account_transfer_details` (`id`) +) +COLLATE='utf8_general_ci' +ENGINE=InnoDB +; +
