From a7d4c83e9258ca1865d42750dd4c5ad496a05f44 Mon Sep 17 00:00:00 2001 From: Angel Cajas Date: Mon, 30 Jul 2018 16:52:17 -0600 Subject: [PATCH] A new rates module was added to define new rates that can be used to set min and max nominal interest rate, when a new loan account is created they can be used to determine which rates may be applicable for the loan account. --- .../service/CommandWrapperBuilder.java | 17 ++ .../domain/ConfigurationDomainService.java | 2 + .../domain/ConfigurationDomainServiceJpa.java | 11 + .../api/LoanTransactionsApiResource.java | 6 +- .../loanaccount/api/LoansApiResource.java | 63 +++--- .../loanaccount/data/LoanAccountData.java | 71 +++++-- .../data/LoanChargePaidByData.java | 15 ++ .../loanaccount/data/LoanTransactionData.java | 10 + .../portfolio/loanaccount/domain/Loan.java | 38 +++- ...ateLoanScheduleQueryFromApiJsonHelper.java | 3 +- ...anApplicationCommandFromApiJsonHelper.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 13 +- .../loanaccount/service/LoanAssembler.java | 18 +- .../LoanChargePaidByReadPlatformService.java | 27 +++ ...anChargePaidByReadPlatformServiceImpl.java | 82 +++++++ .../loanproduct/LoanProductConstants.java | 1 + .../api/LoanProductsApiResource.java | 24 ++- .../loanproduct/data/LoanProductData.java | 43 +++- .../loanproduct/domain/LoanProduct.java | 51 ++++- .../domain/LoanProductMinMaxConstraints.java | 8 + .../domain/LoanProductRelatedDetail.java | 3 + .../LoanProductDataValidator.java | 3 +- .../LoanProductReadPlatformServiceImpl.java | 22 +- ...WritePlatformServiceJpaRepositoryImpl.java | 40 +++- .../portfolio/rate/api/RateApiConstants.java | 29 +++ .../portfolio/rate/api/RateApiResource.java | 137 ++++++++++++ .../portfolio/rate/data/RateData.java | 53 +++++ .../fineract/portfolio/rate/domain/Rate.java | 201 ++++++++++++++++++ .../portfolio/rate/domain/RateRepository.java | 36 ++++ .../rate/domain/RateRepositoryWrapper.java | 70 ++++++ .../exception/RateAlreadyExistException.java | 30 +++ .../rate/exception/RateNotFoundException.java | 35 +++ .../handler/CreateRateCommandHandler.java | 48 +++++ .../handler/UpdateRateCommandHandler.java | 51 +++++ ...initionCommandFromApiJsonDeserializer.java | 129 +++++++++++ .../portfolio/rate/service/RateAssembler.java | 80 +++++++ .../rate/service/RateReadService.java | 44 ++++ .../rate/service/RateReadServiceImpl.java | 150 +++++++++++++ .../rate/service/RateWriteService.java | 34 +++ .../rate/service/RateWriteServiceImpl.java | 157 ++++++++++++++ .../sql/migrations/core_db/V352__rates.sql | 67 ++++++ 41 files changed, 1841 insertions(+), 83 deletions(-) create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java create mode 100644 fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index 72bb97ac9..f713c1beb 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -3147,4 +3147,21 @@ public class CommandWrapperBuilder { this.href = "/self/pocket?command="+PocketApiConstants.delinkAccountsFromPocketCommandParam; return this; } + + public CommandWrapperBuilder createRate() { + this.actionName = "CREATE"; + this.entityName = "RATE"; + this.entityId = null; + this.href = "/rates/template"; + return this; + } + + public CommandWrapperBuilder updateRate(final Long rateId) { + this.actionName = "UPDATE"; + this.entityName = "RATE"; + this.entityId = rateId; + this.href = "/rates/" + rateId; + return this; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 13f83de3e..7c66561bb 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -92,4 +92,6 @@ public interface ConfigurationDomainService { Integer retrieveOTPCharacterLength(); Integer retrieveOTPLiveTime(); + + boolean isSubRatesEnabled(); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index b534a1b37..54a955f93 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -332,4 +332,15 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService } return configurations.get(key); } + + @Override + public boolean isSubRatesEnabled() { + GlobalConfigurationPropertyData configuration = getGlobalConfigurationPropertyData("sub-rates"); + if (configuration==null){ + return false; + }else{ + return configuration.isEnabled(); + } + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java index 187fdf19d..fd88a67c3 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java @@ -54,6 +54,7 @@ import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSer import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData; +import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; @@ -82,19 +83,21 @@ public class LoanTransactionsApiResource { private final DefaultToApiJsonSerializer toApiJsonSerializer; private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; + private final LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService; @Autowired public LoanTransactionsApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService, final ApiRequestParameterHelper apiRequestParameterHelper, final DefaultToApiJsonSerializer toApiJsonSerializer, final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - PaymentTypeReadPlatformService paymentTypeReadPlatformService) { + PaymentTypeReadPlatformService paymentTypeReadPlatformService, LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService) { this.context = context; this.loanReadPlatformService = loanReadPlatformService; this.apiRequestParameterHelper = apiRequestParameterHelper; this.toApiJsonSerializer = toApiJsonSerializer; this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; + this.loanChargePaidByReadPlatformService = loanChargePaidByReadPlatformService; } private boolean is(final String commandParam, final String commandValue) { @@ -170,6 +173,7 @@ public class LoanTransactionsApiResource { this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); LoanTransactionData transactionData = this.loanReadPlatformService.retrieveLoanTransaction(loanId, transactionId); + transactionData.setLoanChargePaidByList(this.loanChargePaidByReadPlatformService.getLoanChargesPaidByTransactionId(transactionId)); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters()); if (settings.isTemplate()) { final Collection paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes(); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java index 8e6a77faf..7b65d8a48 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java @@ -64,6 +64,7 @@ import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookP import org.apache.fineract.infrastructure.bulkimport.service.BulkImportWorkbookService; import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.api.JsonQuery; @@ -131,6 +132,8 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatform import org.apache.fineract.portfolio.note.data.NoteData; import org.apache.fineract.portfolio.note.domain.NoteType; import org.apache.fineract.portfolio.note.service.NoteReadPlatformServiceImpl; +import org.apache.fineract.portfolio.rate.data.RateData; +import org.apache.fineract.portfolio.rate.service.RateReadService; import org.apache.fineract.portfolio.savings.DepositAccountType; import org.apache.fineract.portfolio.savings.domain.SavingsAccountStatusType; import org.springframework.beans.factory.annotation.Autowired; @@ -259,7 +262,7 @@ public class LoansApiResource { "syncDisbursementWithMeeting", "loanCounter", "loanProductCounter", "notes", "accountLinkingOptions", "linkedAccount", "interestRateDifferential", "isFloatingInterestRate", "interestRatesPeriods", LoanApiConstants.canUseForTopup, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.topupAmount, LoanApiConstants.clientActiveLoanOptions, - LoanApiConstants.datatables)); + LoanApiConstants.datatables, LoanProductConstants.ratesParamName)); private final Set LOAN_APPROVAL_DATA_PARAMETERS = new HashSet<>(Arrays.asList("approvalDate", "approvalAmount")); private final String resourceNameForPermissions = "LOAN"; @@ -291,30 +294,32 @@ public class LoansApiResource { private final EntityDatatableChecksReadService entityDatatableChecksReadService; private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; - + private final RateReadService rateReadService; + private final ConfigurationDomainService configurationDomainService; @Autowired public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService, - final LoanProductReadPlatformService loanProductReadPlatformService, - final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, - final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService, - final CollateralReadPlatformService loanCollateralReadPlatformService, - final LoanScheduleCalculationPlatformService calculationPlatformService, - final GuarantorReadPlatformService guarantorReadPlatformService, - final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService, - final DefaultToApiJsonSerializer toApiJsonSerializer, - final DefaultToApiJsonSerializer loanApprovalDataToApiJsonSerializer, - final DefaultToApiJsonSerializer loanScheduleToApiJsonSerializer, - final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper, - final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, - final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService, - final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl, - final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, - final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService, - final AccountDetailsReadPlatformService accountDetailsReadPlatformService, - final EntityDatatableChecksReadService entityDatatableChecksReadService, - final BulkImportWorkbookService bulkImportWorkbookService, - final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService) { + final LoanProductReadPlatformService loanProductReadPlatformService, + final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService, + final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService, + final CollateralReadPlatformService loanCollateralReadPlatformService, + final LoanScheduleCalculationPlatformService calculationPlatformService, + final GuarantorReadPlatformService guarantorReadPlatformService, + final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final DefaultToApiJsonSerializer loanApprovalDataToApiJsonSerializer, + final DefaultToApiJsonSerializer loanScheduleToApiJsonSerializer, + final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformServiceImpl noteReadPlatformService, + final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl, + final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, + final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService, + final AccountDetailsReadPlatformService accountDetailsReadPlatformService, + final EntityDatatableChecksReadService entityDatatableChecksReadService, + final BulkImportWorkbookService bulkImportWorkbookService, + final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService, final RateReadService rateReadService, + final ConfigurationDomainService configurationDomainService) { this.context = context; this.loanReadPlatformService = loanReadPlatformService; this.loanProductReadPlatformService = loanProductReadPlatformService; @@ -340,8 +345,10 @@ public class LoansApiResource { this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService; this.accountDetailsReadPlatformService = accountDetailsReadPlatformService; this.entityDatatableChecksReadService = entityDatatableChecksReadService; + this.rateReadService = rateReadService; this.bulkImportWorkbookService=bulkImportWorkbookService; this.bulkImportWorkbookPopulatorService=bulkImportWorkbookPopulatorService; + this.configurationDomainService = configurationDomainService; } /* @@ -397,6 +404,7 @@ public class LoansApiResource { LoanAccountData newLoanAccount = null; Long officeId = null; Collection accountLinkingOptions = null; + boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled(); if (productId != null) { newLoanAccount = this.loanReadPlatformService.retrieveLoanProductDetailsTemplate(productId, clientId, groupId); @@ -474,7 +482,7 @@ public class LoansApiResource { // add product options, allowed loan officers and calendar options // (calendar options will be null in individual loan) newLoanAccount = LoanAccountData.associationsAndTemplate(newLoanAccount, productOptions, allowedLoanOfficers, calendarOptions, - accountLinkingOptions); + accountLinkingOptions, isRatesEnabled); } final List datatableTemplates = this.entityDatatableChecksReadService .retrieveTemplates(StatusEnum.CREATE.getCode().longValue(), EntityTables.LOAN.getName(), productId); @@ -736,13 +744,20 @@ public class LoansApiResource { paidInAdvanceTemplate = this.loanReadPlatformService.retrieveTotalPaidInAdvance(loanId); + //Get rates from Loan + boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled(); + List rates = null; + if (isRatesEnabled){ + rates = this.rateReadService.retrieveLoanRates(loanId); + } + final LoanAccountData loanAccount = LoanAccountData.associationsAndTemplate(loanBasicDetails, repaymentSchedule, loanRepayments, charges, collateral, guarantors, meeting, productOptions, loanTermFrequencyTypeOptions, repaymentFrequencyTypeOptions, repaymentFrequencyNthDayTypeOptions, repaymentFrequencyDayOfWeekTypeOptions, repaymentStrategyOptions, interestRateFrequencyTypeOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions, loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations, - overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions); + overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled); final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(), mandatoryResponseParameters); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java index d5291ff6e..052efe09b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanAccountData.java @@ -50,6 +50,7 @@ import org.apache.fineract.portfolio.loanproduct.data.TransactionProcessingStrat import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductValueConditionType; import org.apache.fineract.portfolio.note.data.NoteData; +import org.apache.fineract.portfolio.rate.data.RateData; import org.joda.time.LocalDate; import org.springframework.util.CollectionUtils; @@ -219,6 +220,10 @@ public class LoanAccountData { private List datatables = null; private final Boolean isEqualAmortization; + //Rate + private final List rates; + private final Boolean isRatesEnabled; + //import fields private String dateFormat; private String locale; @@ -311,6 +316,7 @@ public class LoanAccountData { this.groupId=groupId; this.expectedDisbursementDate=expectedDisbursementDate; this.charges = charges; + this.rates=null; this.id = null; this.accountNo = null; @@ -418,6 +424,7 @@ public class LoanAccountData { this.minimumGap = null; this.maximumGap = null; this.isEqualAmortization = null; + this.isRatesEnabled = false; } @@ -580,6 +587,9 @@ public class LoanAccountData { final String closureLoanAccountNo = null; final BigDecimal topupAmount = null; final boolean isEqualAmortization = false; + final List rates = null; + final Boolean isRatesEnabled = false; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, @@ -598,7 +608,7 @@ public class LoanAccountData { maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, - maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization); + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, rates, isRatesEnabled); } @@ -720,6 +730,9 @@ public class LoanAccountData { final String closureLoanAccountNo = null; final BigDecimal topupAmount = null; final boolean isEqualAmortization = false; + final List rates = null; + final Boolean isRatesEnabled = false; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, @@ -738,7 +751,8 @@ public class LoanAccountData { maxOutstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, - maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization); + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, + rates, isRatesEnabled); } @@ -768,7 +782,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, + acc.rates, acc.isRatesEnabled); } /** @@ -891,6 +906,9 @@ public class LoanAccountData { final String closureLoanAccountNo = null; final BigDecimal topupAmount = null; final boolean isEqualAmortization = false; + final List rates = null; + final Boolean isRatesEnabled = false; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, currencyData, proposedPrincipal, principal, principal, @@ -909,7 +927,8 @@ public class LoanAccountData { maxOutstandingBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, - maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization); + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, + rates, isRatesEnabled); } @@ -939,7 +958,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, + acc.rates, acc.isRatesEnabled); } @@ -1069,6 +1089,9 @@ public class LoanAccountData { final Long closureLoanId = null; final String closureLoanAccountNo = null; final BigDecimal topupAmount = null; + final List rates = null; + final Boolean isRatesEnabled = false; + return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, product.getId(), product.getName(), product.getDescription(), product.isLinkedToFloatingInterestRates(), product.getFundId(), product.getFundName(), loanPurposeId, loanPurposeName, loanOfficerId, loanOfficerName, @@ -1093,7 +1116,7 @@ public class LoanAccountData { originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, - closureLoanAccountNo, topupAmount, product.isEqualAmortization()); + closureLoanAccountNo, topupAmount, product.isEqualAmortization(),rates, isRatesEnabled); } public static LoanAccountData populateLoanProductDefaults(final LoanAccountData acc, final LoanProductData product) { @@ -1153,7 +1176,8 @@ public class LoanAccountData { product.toLoanInterestRecalculationData(), acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, acc.interestRatesPeriods, product.isVariableInstallmentsAllowed(), product.getMinimumGapBetweenInstallments(), product.getMaximumGapBetweenInstallments(), acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization()); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, product.isEqualAmortization(), acc.rates, + acc.isRatesEnabled); } @@ -1222,6 +1246,8 @@ public class LoanAccountData { final PaidInAdvanceData paidInAdvance = null; final Collection interestRatesPeriods = null; final Collection clientActiveLoanOptions = null; + final List rates = null; + final Boolean isRatesEnabled = false; return new LoanAccountData(id, accountNo, status, externalId, clientId, clientAccountNo, clientName, clientOfficeId, group, loanType, loanProductId, loanProductName, loanProductDescription, isLoanProductLinkedToFloatingRate, fundId, fundName, @@ -1241,7 +1267,8 @@ public class LoanAccountData { outstandingLoanBalance, emiAmountVariations, memberVariations, product, inArrears, graceOnArrearsAgeing, overdueCharges, isNPA, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, interestRecalculationData, originalSchedule, createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, isVariableInstallmentsAllowed, minimumGap, - maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization); + maximumGap, subStatus, canUseForTopup, clientActiveLoanOptions, isTopup, closureLoanId, closureLoanAccountNo, topupAmount, isEqualAmortization, + rates, isRatesEnabled); } /* @@ -1265,7 +1292,7 @@ public class LoanAccountData { final PortfolioAccountData linkedAccount, final Collection disbursementDetails, final Collection emiAmountVariations, final Collection overdueCharges, final PaidInAdvanceData paidInAdvance, Collection interestRatesPeriods, - final Collection clientActiveLoanOptions) { + final Collection clientActiveLoanOptions,final List rates, final Boolean isRatesEnabled) { LoanProductConfigurableAttributes loanProductConfigurableAttributes = null; if (acc.product != null) { loanProductConfigurableAttributes = acc.product.getloanProductConfigurableAttributes(); @@ -1294,12 +1321,12 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, paidInAdvance, interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, clientActiveLoanOptions, acc.isTopup, - acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, rates, isRatesEnabled); } public static LoanAccountData associationsAndTemplate(final LoanAccountData acc, final Collection productOptions, final Collection allowedLoanOfficers, final Collection calendarOptions, - final Collection accountLinkingOptions) { + final Collection accountLinkingOptions, final Boolean isRatesEnabled) { return associationsAndTemplate(acc, acc.repaymentSchedule, acc.transactions, acc.charges, acc.collateral, acc.guarantors, acc.meeting, productOptions, acc.termFrequencyTypeOptions, acc.repaymentFrequencyTypeOptions, acc.repaymentFrequencyNthDayTypeOptions, acc.repaymentFrequencyDaysOfWeekTypeOptions, @@ -1307,7 +1334,7 @@ public class LoanAccountData { acc.interestTypeOptions, acc.interestCalculationPeriodTypeOptions, acc.fundOptions, acc.chargeOptions, null, allowedLoanOfficers, acc.loanPurposeOptions, acc.loanCollateralOptions, calendarOptions, acc.notes, accountLinkingOptions, acc.linkedAccount, acc.disbursementDetails, acc.emiAmountVariations, acc.overdueCharges, acc.paidInAdvance, - acc.interestRatesPeriods, acc.clientActiveLoanOptions); + acc.interestRatesPeriods, acc.clientActiveLoanOptions,acc.rates, isRatesEnabled); } public static LoanAccountData associateGroup(final LoanAccountData acc, final GroupGeneralData group) { @@ -1336,7 +1363,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, + acc.isRatesEnabled); } public static LoanAccountData associateMemberVariations(final LoanAccountData acc, final Map memberLoanCycle) { @@ -1401,7 +1429,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, + acc.isRatesEnabled); } @@ -1435,7 +1464,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, acc.rates, + acc.isRatesEnabled); } public static LoanAccountData withLoanCalendarData(final LoanAccountData acc, final CalendarData calendarData) { @@ -1463,7 +1493,8 @@ public class LoanAccountData { acc.isNPA, acc.daysInMonthType, acc.daysInYearType, acc.isInterestRecalculationEnabled, acc.interestRecalculationData, acc.originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, + acc.rates, acc.isRatesEnabled); } public static LoanAccountData withOriginalSchedule(final LoanAccountData acc, final LoanScheduleData originalSchedule) { @@ -1492,7 +1523,8 @@ public class LoanAccountData { acc.isInterestRecalculationEnabled, acc.interestRecalculationData, originalSchedule, acc.createStandingInstructionAtDisbursement, acc.paidInAdvance, acc.interestRatesPeriods, acc.isVariableInstallmentsAllowed, acc.minimumGap, acc.maximumGap, acc.subStatus, acc.canUseForTopup, - acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization); + acc.clientActiveLoanOptions, acc.isTopup, acc.closureLoanId, acc.closureLoanAccountNo, acc.topupAmount, acc.isEqualAmortization, + acc.rates, acc.isRatesEnabled); } private LoanAccountData(final Long id, // @@ -1542,7 +1574,8 @@ public class LoanAccountData { final Collection interestRatesPeriods, final Boolean isVariableInstallmentsAllowed, final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup, final Collection clientActiveLoanOptions, final boolean isTopup, - final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization) { + final Long closureLoanId, final String closureLoanAccountNo, final BigDecimal topupAmount, final boolean isEqualAmortization, + final List rates, final Boolean isRatesEnabled) { this.id = id; this.accountNo = accountNo; @@ -1622,6 +1655,7 @@ public class LoanAccountData { this.amortizationTypeOptions = amortizationTypeOptions; this.interestTypeOptions = interestTypeOptions; this.interestCalculationPeriodTypeOptions = interestCalculationPeriodTypeOptions; + this.isRatesEnabled = isRatesEnabled; if (CollectionUtils.isEmpty(transactionProcessingStrategyOptions)) { this.transactionProcessingStrategyOptions = null; @@ -1725,6 +1759,7 @@ public class LoanAccountData { this.closureLoanAccountNo = closureLoanAccountNo; this.topupAmount = topupAmount; this.isEqualAmortization = isEqualAmortization; + this.rates = rates; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java index 9978bcd5e..3545b6cfb 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanChargePaidByData.java @@ -27,6 +27,17 @@ public class LoanChargePaidByData { private final Integer installmentNumber; private final Long chargeId; private final Long transactionId; + private final String name; + + public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId, + final Long transactionId, String name) { + this.id = id; + this.amount = amount; + this.installmentNumber = installmentNumber; + this.chargeId = chargeId; + this.transactionId = transactionId; + this.name=name; + } public LoanChargePaidByData(final Long id, final BigDecimal amount, final Integer installmentNumber, final Long chargeId, final Long transactionId) { @@ -35,6 +46,7 @@ public class LoanChargePaidByData { this.installmentNumber = installmentNumber; this.chargeId = chargeId; this.transactionId = transactionId; + this.name=null; } public Long getId() { @@ -57,4 +69,7 @@ public class LoanChargePaidByData { return this.transactionId; } + public String getName() { + return name; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java index b986b34a1..fc157d125 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java @@ -59,6 +59,8 @@ public class LoanTransactionData { private final boolean manuallyReversed; private final LocalDate possibleNextRepaymentDate; + private Collection loanChargePaidByList; + // templates final Collection paymentTypeOptions; @@ -339,4 +341,12 @@ public class LoanTransactionData { public void setWriteOffReasonOptions(Collection writeOffReasonOptions){ this.writeOffReasonOptions =writeOffReasonOptions; } + + public Collection getLoanChargePaidByList() { + return loanChargePaidByList; + } + + public void setLoanChargePaidByList(Collection loanChargePaidByList) { + this.loanChargePaidByList = loanChargePaidByList; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index b44aa4163..201d7715b 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -133,6 +133,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessin import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; +import org.apache.fineract.portfolio.rate.domain.Rate; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.Days; import org.joda.time.LocalDate; @@ -388,20 +389,24 @@ public class Loan extends AbstractPersistableCustom { @OneToOne(cascade = CascadeType.ALL, mappedBy = "loan", optional = true, orphanRemoval = true, fetch=FetchType.EAGER) private LoanTopupDetails loanTopupDetails; + @OneToMany(fetch = FetchType.LAZY) + @JoinTable(name = "m_loan_rate", joinColumns = @JoinColumn(name = "loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id")) + private List rates; + public static Loan newIndividualLoanApplication(final String accountNo, final Client client, final Integer loanType, final LoanProduct loanProduct, final Fund fund, final Staff officer, final CodeValue loanPurpose, final LoanTransactionProcessingStrategy transactionProcessingStrategy, final LoanProductRelatedDetail loanRepaymentScheduleDetail, final Set loanCharges, final Set collateral, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, - final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) { + final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List rates) { final LoanStatus status = null; final Group group = null; final Boolean syncDisbursementWithMeeting = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential); + interestRateDifferential, rates); } public static Loan newGroupLoanApplication(final String accountNo, final Group group, final Integer loanType, @@ -411,13 +416,13 @@ public class Loan extends AbstractPersistableCustom { final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential) { + final BigDecimal interestRateDifferential, final List rates) { final LoanStatus status = null; final Client client = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential); + interestRateDifferential, rates); } public static Loan newIndividualLoanApplicationFromGroup(final String accountNo, final Client client, final Group group, @@ -427,12 +432,12 @@ public class Loan extends AbstractPersistableCustom { final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate, - final BigDecimal interestRateDifferential) { + final BigDecimal interestRateDifferential, final List rates) { final LoanStatus status = null; return new Loan(accountNo, client, group, loanType, fund, officer, loanPurpose, transactionProcessingStrategy, loanProduct, loanRepaymentScheduleDetail, status, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate, - interestRateDifferential); + interestRateDifferential,rates); } protected Loan() { @@ -445,7 +450,7 @@ public class Loan extends AbstractPersistableCustom { final Set loanCharges, final Set collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount, final List disbursementDetails, final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement, - final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) { + final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List rates) { this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail; this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings(); @@ -503,6 +508,9 @@ public class Loan extends AbstractPersistableCustom { this.proposedPrincipal = this.loanRepaymentScheduleDetail.getPrincipal().getAmount(); + //rates added here + this.rates = rates; + } private LoanSummary updateSummaryWithTotalFeeChargesDueAtDisbursement(final BigDecimal feeChargesDueAtDisbursement) { @@ -1166,6 +1174,14 @@ public class Loan extends AbstractPersistableCustom { this.collateral.addAll(associateWithThisLoan(loanCollateral)); } + public void updateLoanRates(final List loanRates) { + if (this.rates == null) { + this.rates = new ArrayList<>(); + } + this.rates.clear(); + this.rates.addAll(loanRates); + } + public void updateLoanSchedule(final LoanScheduleModel modifiedLoanSchedule, AppUser currentUser) { this.repaymentScheduleInstallments.clear(); for (final LoanScheduleModelPeriod scheduledLoanInstallment : modifiedLoanSchedule.getPeriods()) { @@ -6533,4 +6549,12 @@ public class Loan extends AbstractPersistableCustom { } public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();} + + public void setRates(List rates) { + this.rates = rates; + } + + public List getRates() { + return rates; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java index 8c85637fc..347898971 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/CalculateLoanScheduleQueryFromApiJsonHelper.java @@ -67,7 +67,8 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper { LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isFloatingInterestRateParameterName, LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, - LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam)); + LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, + LoanProductConstants.ratesParamName)); private final FromJsonHelper fromApiJsonHelper; 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 0d36e4ba5..24992bc1c 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,7 @@ public final class LoanApplicationCommandFromApiJsonHelper { LoanApiConstants.linkAccountIdParameterName, LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName, LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.graceOnArrearsAgeingParameterName, LoanApiConstants.createStandingInstructionAtDisbursementParameterName, - LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam)); + LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose, LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.ratesParamName)); private final FromJsonHelper fromApiJsonHelper; private final CalculateLoanScheduleQueryFromApiJsonHelper apiJsonHelper; 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 b068558c4..9995a39c7 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 @@ -126,6 +126,7 @@ import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataVa import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.note.domain.Note; import org.apache.fineract.portfolio.note.domain.NoteRepository; +import org.apache.fineract.portfolio.rate.service.RateAssembler; import org.apache.fineract.portfolio.savings.domain.SavingsAccount; import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler; import org.apache.fineract.useradministration.domain.AppUser; @@ -178,6 +179,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa private final FineractEntityToEntityMappingRepository repository; private final FineractEntityRelationRepository fineractEntityRelationRepository; private final LoanProductReadPlatformService loanProductReadPlatformService; + private final RateAssembler rateAssembler; @Autowired public LoanApplicationWritePlatformServiceJpaRepositoryImpl(final PlatformSecurityContext context, final FromJsonHelper fromJsonHelper, @@ -200,7 +202,8 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa final LoanScheduleAssembler loanScheduleAssembler, final LoanUtilService loanUtilService, final CalendarReadPlatformService calendarReadPlatformService, final GlobalConfigurationRepositoryWrapper globalConfigurationRepository, final FineractEntityToEntityMappingRepository repository, final FineractEntityRelationRepository fineractEntityRelationRepository, - final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService) { + final EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, final LoanProductReadPlatformService loanProductReadPlatformService, + final RateAssembler rateAssembler) { this.context = context; this.fromJsonHelper = fromJsonHelper; this.loanApplicationTransitionApiJsonValidator = loanApplicationTransitionApiJsonValidator; @@ -236,7 +239,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa this.repository = repository; this.fineractEntityRelationRepository = fineractEntityRelationRepository; this.loanProductReadPlatformService = loanProductReadPlatformService; - + this.rateAssembler = rateAssembler; } private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() { @@ -858,6 +861,12 @@ public void checkForProductMixRestrictions(final Loan loan) { existingLoanApplication.recalculateAllCharges(); } + //Changes to modify loan rates. + if (command.hasParameter(LoanProductConstants.ratesParamName)) { + existingLoanApplication.updateLoanRates(rateAssembler.fromParsedJson(command.parsedJson())); + } + + this.fromApiJsonDeserializer.validateLoanTermAndRepaidEveryValues(existingLoanApplication.getTermFrequency(), existingLoanApplication.getTermPeriodFrequencyType(), productRelatedDetail.getNumberOfRepayments(), productRelatedDetail.getRepayEvery(), productRelatedDetail.getRepaymentPeriodFrequencyType().getValue(), diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java index 1892efe71..094b2e88f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAssembler.java @@ -25,6 +25,8 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Set; + +import com.google.gson.JsonArray; import org.apache.fineract.infrastructure.codes.domain.CodeValue; import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -71,6 +73,7 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel; import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler; +import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; @@ -78,6 +81,8 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessin import org.apache.fineract.portfolio.loanproduct.exception.InvalidCurrencyException; import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.rate.service.RateAssembler; import org.apache.fineract.useradministration.domain.AppUser; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; @@ -104,6 +109,7 @@ public class LoanAssembler { private final ConfigurationDomainService configurationDomainService; private final WorkingDaysRepositoryWrapper workingDaysRepository; private final LoanUtilService loanUtilService; + private final RateAssembler rateAssembler; @Autowired public LoanAssembler(final FromJsonHelper fromApiJsonHelper, final LoanRepositoryWrapper loanRepository, @@ -115,7 +121,7 @@ public class LoanAssembler { final CollateralAssembler loanCollateralAssembler, final LoanSummaryWrapper loanSummaryWrapper, final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory, final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService, - final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService) { + final WorkingDaysRepositoryWrapper workingDaysRepository, final LoanUtilService loanUtilService, RateAssembler rateAssembler) { this.fromApiJsonHelper = fromApiJsonHelper; this.loanRepository = loanRepository; this.loanProductRepository = loanProductRepository; @@ -134,6 +140,7 @@ public class LoanAssembler { this.configurationDomainService = configurationDomainService; this.workingDaysRepository = workingDaysRepository; this.loanUtilService = loanUtilService; + this.rateAssembler = rateAssembler; } public Loan assembleFrom(final Long accountId) { @@ -222,6 +229,9 @@ public class LoanAssembler { Client client = null; Group group = null; + //Here we add Rates to LoanApplication + final List rates = this.rateAssembler.fromParsedJson(element); + final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element); final BigDecimal interestRateDifferential = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element); @@ -250,21 +260,21 @@ public class LoanAssembler { loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential); + createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates); } else if (group != null) { loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, - createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential); + createStandingInstructionAtDisbursement,isFloatingInterestRate, interestRateDifferential, rates); } else if (client != null) { loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund, loanOfficer, loanPurpose, loanTransactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, - isFloatingInterestRate, interestRateDifferential); + isFloatingInterestRate, interestRateDifferential, rates); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java new file mode 100644 index 000000000..1ca6dc172 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformService.java @@ -0,0 +1,27 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.loanaccount.service; + +import java.util.List; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; + +public interface LoanChargePaidByReadPlatformService { + + List getLoanChargesPaidByTransactionId(Long id); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java new file mode 100644 index 000000000..60cafe089 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargePaidByReadPlatformServiceImpl.java @@ -0,0 +1,82 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.loanaccount.service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class LoanChargePaidByReadPlatformServiceImpl implements + LoanChargePaidByReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final PlatformSecurityContext context; + + @Autowired + public LoanChargePaidByReadPlatformServiceImpl(final PlatformSecurityContext context, + final RoutingDataSource dataSource) { + this.context = context; + this.jdbcTemplate = new JdbcTemplate(dataSource); + + } + + @Override + public List getLoanChargesPaidByTransactionId(Long transactionId) { + this.context.authenticatedUser(); + final LoanChargePaidByMapper rm = new LoanChargePaidByMapper(); + final String sql = "select " + rm.loanChargePaidBySchema() + " where lcpd.loan_transaction_id = ?"; + return this.jdbcTemplate.query(sql, rm, new Object[]{transactionId}); + } + + private static final class LoanChargePaidByMapper implements RowMapper { + + public String loanChargePaidBySchema() { + return "lcpd.id as id, lcpd.amount as amount, lcpd.installment_number as installmentNumber," + + " lcpd.loan_charge_id as chargeId, lcpd.loan_transaction_id as transactionId, " + + " c.name as chargeName" + + " from m_loan_charge_paid_by lcpd" + + " join m_loan_charge lc on lc.id=lcpd.loan_charge_Id" + + " join m_charge c on c.id=lc.charge_id"; + } + + @Override + public LoanChargePaidByData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) + throws SQLException { + final Long id = rs.getLong("id"); + final BigDecimal amount = rs.getBigDecimal("amount"); + final Integer installmentNumber = rs.getInt("installmentNumber"); + final Long chargeId = rs.getLong("chargeId"); + final Long transactionId = rs.getLong("transactionId"); + final String chargeName = rs.getString("chargeName"); + return new LoanChargePaidByData(id, amount, installmentNumber, chargeId, transactionId, chargeName); + } + + } + +} 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 471d4f963..068ee9e25 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 @@ -134,5 +134,6 @@ public interface LoanProductConstants { public static final String isEqualAmortizationParam = "isEqualAmortization"; + public static final String ratesParamName = "rates"; } 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 376ea36b1..7d792465c 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 @@ -52,6 +52,7 @@ import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToG import org.apache.fineract.commands.domain.CommandWrapper; import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.api.ApiParameterHelper; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; @@ -77,6 +78,8 @@ import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatfor import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatformService; +import org.apache.fineract.portfolio.rate.data.RateData; +import org.apache.fineract.portfolio.rate.service.RateReadService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -102,7 +105,8 @@ public class LoanProductsApiResource { "interestCalculationPeriodTypeOptions", "transactionProcessingStrategyOptions", "chargeOptions", "accountingOptions", "accountingRuleOptions", "accountingMappingOptions", "floatingRateOptions", "isLinkedToFloatingInterestRates", "floatingRatesId", "interestRateDifferential", "minDifferentialLendingRate", "defaultDifferentialLendingRate", - "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam)); + "maxDifferentialLendingRate", "isFloatingInterestRateCalculationAllowed", LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam, + LoanProductConstants.ratesParamName)); private final Set PRODUCT_MIX_DATA_PARAMETERS = new HashSet<>(Arrays.asList("productId", "productName", "restrictedProducts", "allowedProducts", "productOptions")); @@ -125,6 +129,9 @@ public class LoanProductsApiResource { private final DropdownReadPlatformService commonDropdownReadPlatformService; private final PaymentTypeReadPlatformService paymentTypeReadPlatformService; private final FloatingRatesReadPlatformService floatingRateReadPlatformService; + private final RateReadService rateReadService; + private final ConfigurationDomainService configurationDomainService; + @Autowired public LoanProductsApiResource(final PlatformSecurityContext context, final LoanProductReadPlatformService readPlatformService, @@ -139,7 +146,8 @@ public class LoanProductsApiResource { final ProductMixReadPlatformService productMixReadPlatformService, final DropdownReadPlatformService commonDropdownReadPlatformService, PaymentTypeReadPlatformService paymentTypeReadPlatformService, - final FloatingRatesReadPlatformService floatingRateReadPlatformService) { + final FloatingRatesReadPlatformService floatingRateReadPlatformService, final RateReadService rateReadService, + final ConfigurationDomainService configurationDomainService) { this.context = context; this.loanProductReadPlatformService = readPlatformService; this.chargeReadPlatformService = chargeReadPlatformService; @@ -156,6 +164,8 @@ public class LoanProductsApiResource { this.commonDropdownReadPlatformService = commonDropdownReadPlatformService; this.paymentTypeReadPlatformService = paymentTypeReadPlatformService; this.floatingRateReadPlatformService = floatingRateReadPlatformService; + this.rateReadService = rateReadService; + this.configurationDomainService = configurationDomainService; } @POST @@ -288,6 +298,12 @@ public class LoanProductsApiResource { penaltyOptions = null; } + boolean isRatesEnabled = this.configurationDomainService.isSubRatesEnabled(); + Collection rateOptions = this.rateReadService.retrieveLoanApplicableRates(); + if(rateOptions.isEmpty()){ + rateOptions = null; + } + final Collection currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies(); final List amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions(); final List interestTypeOptions = this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions(); @@ -331,11 +347,11 @@ public class LoanProductsApiResource { return new LoanProductData(productData, chargeOptions, penaltyOptions, paymentTypeOptions, currencyOptions, amortizationTypeOptions, interestTypeOptions, interestCalculationPeriodTypeOptions, repaymentFrequencyTypeOptions, - interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, accountOptions, + interestRateFrequencyTypeOptions, fundOptions, transactionProcessingStrategyOptions, rateOptions, accountOptions, accountingRuleTypeOptions, loanCycleValueConditionTypeOptions, daysInMonthTypeOptions, daysInYearTypeOptions, interestRecalculationCompoundingTypeOptions, rescheduleStrategyTypeOptions, interestRecalculationFrequencyTypeOptions, preCloseInterestCalculationStrategyOptions, floatingRateOptions, interestRecalculationNthDayTypeOptions, - interestRecalculationDayOfWeekTypeOptions); + interestRecalculationDayOfWeekTypeOptions, isRatesEnabled); } } \ No newline at end of file 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 92e4b5a83..dd9b83fd5 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 @@ -47,6 +47,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.InterestMethod; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations; import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData; +import org.apache.fineract.portfolio.rate.data.RateData; import org.joda.time.LocalDate; import org.springframework.util.CollectionUtils; @@ -133,6 +134,10 @@ public class LoanProductData implements Serializable { private Collection feeToIncomeAccountMappings; private Collection penaltyToIncomeAccountMappings; + // rates + private final boolean isRatesEnabled; + private final Collection rates; + // template related private final Collection fundOptions; @SuppressWarnings("unused") @@ -146,6 +151,7 @@ public class LoanProductData implements Serializable { private final List interestCalculationPeriodTypeOptions; private final Collection transactionProcessingStrategyOptions; private final Collection chargeOptions; + private final Collection rateOptions; @SuppressWarnings("unused") private final Collection penaltyOptions; @SuppressWarnings("unused") @@ -261,6 +267,9 @@ public class LoanProductData implements Serializable { final boolean syncExpectedWithDisbursementDate = false; final boolean canUseForTopup = false; final boolean isEqualAmortization = false; + final Collection rateOptions = null; + final Collection rates = null; + final boolean isRatesEnabled= false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, minInterestRatePerPeriod, maxInterestRatePerPeriod, annualInterestRate, repaymentFrequencyType, interestRateFrequencyType, @@ -275,7 +284,7 @@ public class LoanProductData implements Serializable { loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); } @@ -353,6 +362,9 @@ public class LoanProductData implements Serializable { final boolean syncExpectedWithDisbursementDate = false; final boolean canUseForTopup = false; final boolean isEqualAmortization = false; + final Collection rateOptions = null; + final Collection rates = null; + final boolean isRatesEnabled = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -368,7 +380,7 @@ public class LoanProductData implements Serializable { loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); } @@ -453,6 +465,9 @@ public class LoanProductData implements Serializable { final boolean syncExpectedWithDisbursementDate = false; final boolean canUseForTopup = false; final boolean isEqualAmortization = false; + final Collection rateOptions = null; + final Collection rates = null; + final boolean isRatesEnabled = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -468,7 +483,7 @@ public class LoanProductData implements Serializable { installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); } @@ -547,6 +562,9 @@ public class LoanProductData implements Serializable { final boolean syncExpectedWithDisbursementDate = false; final boolean canUseForTopup = false; final boolean isEqualAmortization = false; + final Collection rateOptions = null; + final Collection rates = null; + final boolean isRatesEnabled = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -562,7 +580,7 @@ public class LoanProductData implements Serializable { installmentAmountInMultiplesOf, loanProductConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGap, maximumGap, - syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, rates, isRatesEnabled); } @@ -601,7 +619,8 @@ public class LoanProductData implements Serializable { BigDecimal minDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, boolean isFloatingInterestRateCalculationAllowed, final boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, - final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) { + final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, Collection rateOptions, Collection rates, + final boolean isRatesEnabled) { this.id = id; this.name = name; this.shortName = shortName; @@ -653,6 +672,9 @@ public class LoanProductData implements Serializable { this.status = status; this.externalId = externalId; this.minimumDaysBetweenDisbursalAndFirstRepayment = minimumDaysBetweenDisbursalAndFirstRepayment; + this.rateOptions = rateOptions; + this.rates = rates; + this.isRatesEnabled = isRatesEnabled; this.chargeOptions = null; this.penaltyOptions = null; @@ -715,7 +737,7 @@ public class LoanProductData implements Serializable { final List interestTypeOptions, final List interestCalculationPeriodTypeOptions, final List repaymentFrequencyTypeOptions, final List interestRateFrequencyTypeOptions, final Collection fundOptions, final Collection transactionStrategyOptions, - final Map> accountingMappingOptions, final List accountingRuleOptions, + final Collection rateOptions, final Map> accountingMappingOptions, final List accountingRuleOptions, final List valueConditionTypeOptions, final List daysInMonthTypeOptions, final List daysInYearTypeOptions, final List interestRecalculationCompoundingTypeOptions, final List rescheduleStrategyTypeOptions, final List interestRecalculationFrequencyTypeOptions, @@ -780,6 +802,7 @@ public class LoanProductData implements Serializable { this.currency = productData.currency; this.fundOptions = fundOptions; this.transactionProcessingStrategyOptions = transactionStrategyOptions; + this.rateOptions = rateOptions; this.floatingRateOptions = floatingRateOptions; if (this.transactionProcessingStrategyOptions != null && this.transactionProcessingStrategyOptions.size() == 1) { final List listOfOptions = new ArrayList<>(this.transactionProcessingStrategyOptions); @@ -843,6 +866,8 @@ public class LoanProductData implements Serializable { this.syncExpectedWithDisbursementDate = productData.syncExpectedWithDisbursementDate; this.canUseForTopup = productData.canUseForTopup; this.isEqualAmortization = productData.isEqualAmortization; + this.rates = productData.rates; + this.isRatesEnabled = isRatesEnabled; } private Collection nullIfEmpty(final Collection charges) { @@ -1198,9 +1223,9 @@ public class LoanProductData implements Serializable { return syncExpectedWithDisbursementDate; } - public boolean canUseForTopup() { - return this.canUseForTopup; - } + public boolean canUseForTopup() { + return this.canUseForTopup; + } public BigDecimal getInterestRateDifferential() { return this.interestRateDifferential; 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 8aaaa2d91..d64dd5671 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 @@ -61,6 +61,7 @@ import org.apache.fineract.portfolio.floatingrates.domain.FloatingRate; import org.apache.fineract.portfolio.fund.domain.Fund; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; +import org.apache.fineract.portfolio.rate.domain.Rate; import org.joda.time.LocalDate; /** @@ -99,6 +100,10 @@ public class LoanProduct extends AbstractPersistableCustom { @JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id")) private List charges; + @ManyToMany(fetch = FetchType.LAZY) + @JoinTable(name = "m_product_loan_rate", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id")) + private List rates; + @Embedded private LoanProductRelatedDetail loanProductRelatedDetail; @@ -184,7 +189,8 @@ public class LoanProduct extends AbstractPersistableCustom { private boolean isEqualAmortization = false; public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy, - final List productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) { + final List productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate, + final List productRates) { final String name = command.stringValueOfParameterNamed("name"); final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName); @@ -350,7 +356,7 @@ public class LoanProduct extends AbstractPersistableCustom { installmentAmountInMultiplesOf, loanConfigurableAttributes, isLinkedToFloatingInterestRates, floatingRate, interestRateDifferential, minDifferentialLendingRate, maxDifferentialLendingRate, defaultDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableInstallmentsAllowed, minimumGapBetweenInstallments, - maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + maximumGapBetweenInstallments, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, productRates); } @@ -580,7 +586,7 @@ public class LoanProduct extends AbstractPersistableCustom { BigDecimal minDifferentialLendingRate, BigDecimal maxDifferentialLendingRate, BigDecimal defaultDifferentialLendingRate, Boolean isFloatingInterestRateCalculationAllowed, final Boolean isVariableInstallmentsAllowed, final Integer minimumGapBetweenInstallments, final Integer maximumGapBetweenInstallments, - final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization) { + final boolean syncExpectedWithDisbursementDate, final boolean canUseForTopup, final boolean isEqualAmortization, final List rates) { this.fund = fund; this.transactionProcessingStrategy = transactionProcessingStrategy; this.name = name.trim(); @@ -658,6 +664,10 @@ public class LoanProduct extends AbstractPersistableCustom { syncExpectedWithDisbursementDate; this.canUseForTopup = canUseForTopup; this.isEqualAmortization = isEqualAmortization; + + if(rates != null){ + this.rates = rates; + } } public MonetaryCurrency getCurrency() { @@ -699,6 +709,25 @@ public class LoanProduct extends AbstractPersistableCustom { return updated; } + public boolean updateRates(final List newProductRates) { + if (newProductRates == null) { return false; } + + boolean updated = false; + if (this.rates != null) { + final Set currentSetOfCharges = new HashSet<>(this.rates); + final Set newSetOfCharges = new HashSet<>(newProductRates); + + if (!currentSetOfCharges.equals(newSetOfCharges)) { + updated = true; + this.rates = newProductRates; + } + } else { + updated = true; + this.rates = newProductRates; + } + return updated; + } + public Integer getAccountingType() { return this.accountingRule; } @@ -1043,6 +1072,13 @@ public class LoanProduct extends AbstractPersistableCustom { this.canUseForTopup = newValue; } + if (command.hasParameter(LoanProductConstants.ratesParamName)) { + final JsonArray jsonArray = command.arrayOfParameterNamed(LoanProductConstants.ratesParamName); + if (jsonArray != null) { + actualChanges.put(LoanProductConstants.ratesParamName, command.jsonFragment(LoanProductConstants.ratesParamName)); + } + } + return actualChanges; } @@ -1379,4 +1415,13 @@ public class LoanProduct extends AbstractPersistableCustom { this.isEqualAmortization = isEqualAmortization; } + + public List getRates() { + return rates; + } + + public void setRates(List rates) { + this.rates = rates; + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java index 73a239fe2..1e8ece97b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductMinMaxConstraints.java @@ -163,4 +163,12 @@ public class LoanProductMinMaxConstraints { this.maxNominalInterestRatePerPeriod = null; } + + public void setMinNominalInterestRatePerPeriod(BigDecimal minNominalInterestRatePerPeriod) { + this.minNominalInterestRatePerPeriod = minNominalInterestRatePerPeriod; + } + + public void setMaxNominalInterestRatePerPeriod(BigDecimal maxNominalInterestRatePerPeriod) { + this.maxNominalInterestRatePerPeriod = maxNominalInterestRatePerPeriod; + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java index cc0860a64..5846f728d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/LoanProductRelatedDetail.java @@ -666,4 +666,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche this.isEqualAmortization = isEqualAmortization; } + public void setNominalInterestRatePerPeriod(BigDecimal nominalInterestRatePerPeriod) { + this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod; + } } 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 479429870..d676f9aa3 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 @@ -106,7 +106,8 @@ public final class LoanProductDataValidator { LoanProductConstants.recalculationRestFrequencyWeekdayParamName, LoanProductConstants.recalculationRestFrequencyNthDayParamName, LoanProductConstants.recalculationRestFrequencyOnDayParamName, LoanProductConstants.isCompoundingToBePostedAsTransactionParamName, LoanProductConstants.allowCompoundingOnEodParamName, - LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam)); + LoanProductConstants.canUseForTopup, LoanProductConstants.isEqualAmortizationParam, + LoanProductConstants.ratesParamName)); private static final String[] supportedloanConfigurableAttributes = {LoanProductConstants.amortizationTypeParamName, LoanProductConstants.interestTypeParamName, LoanProductConstants.transactionProcessingStrategyIdParamName, 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 cb6283954..36a8c4c7b 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 @@ -42,6 +42,8 @@ import org.apache.fineract.portfolio.loanproduct.data.LoanProductInterestRecalcu import org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductParamType; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; +import org.apache.fineract.portfolio.rate.data.RateData; +import org.apache.fineract.portfolio.rate.service.RateReadService; import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; @@ -55,16 +57,18 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo private final PlatformSecurityContext context; private final JdbcTemplate jdbcTemplate; private final ChargeReadPlatformService chargeReadPlatformService; + private final RateReadService rateReadService; private final FineractEntityAccessUtil fineractEntityAccessUtil; @Autowired public LoanProductReadPlatformServiceImpl(final PlatformSecurityContext context, final ChargeReadPlatformService chargeReadPlatformService, final RoutingDataSource dataSource, - final FineractEntityAccessUtil fineractEntityAccessUtil) { + final FineractEntityAccessUtil fineractEntityAccessUtil, final RateReadService rateReadService) { this.context = context; this.chargeReadPlatformService = chargeReadPlatformService; this.jdbcTemplate = new JdbcTemplate(dataSource); this.fineractEntityAccessUtil = fineractEntityAccessUtil; + this.rateReadService=rateReadService; } @Override @@ -72,8 +76,9 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo try { final Collection charges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanProductId); + final Collection rates = this.rateReadService.retrieveProductLoanRates(loanProductId); final Collection borrowerCycleVariationDatas = retrieveLoanProductBorrowerCycleVariations(loanProductId); - final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas); + final LoanProductMapper rm = new LoanProductMapper(charges, borrowerCycleVariationDatas, rates); final String sql = "select " + rm.loanProductSchema() + " where lp.id = ?"; return this.jdbcTemplate.queryForObject(sql, rm, new Object[] { loanProductId }); @@ -95,7 +100,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo this.context.authenticatedUser(); - final LoanProductMapper rm = new LoanProductMapper(null, null); + final LoanProductMapper rm = new LoanProductMapper(null, null, null); String sql = "select " + rm.loanProductSchema(); @@ -172,10 +177,13 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo private final Collection borrowerCycleVariationDatas; + private final Collection rates; + public LoanProductMapper(final Collection charges, - final Collection borrowerCycleVariationDatas) { + final Collection borrowerCycleVariationDatas, final Collection rates) { this.charges = charges; this.borrowerCycleVariationDatas = borrowerCycleVariationDatas; + this.rates = rates; } public String loanProductSchema() { @@ -448,6 +456,8 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo final boolean syncExpectedWithDisbursementDate = rs.getBoolean("syncExpectedWithDisbursementDate"); final boolean canUseForTopup = rs.getBoolean("canUseForTopup"); + final Collection rateOptions= null; + final boolean isRatesEnabled = false; return new LoanProductData(id, name, shortName, description, currency, principal, minPrincipal, maxPrincipal, tolerance, numberOfRepayments, minNumberOfRepayments, maxNumberOfRepayments, repaymentEvery, interestRatePerPeriod, @@ -464,7 +474,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo installmentAmountInMultiplesOf, allowAttributeOverrides, isLinkedToFloatingInterestRates, floatingRateId, floatingRateName, interestRateDifferential, minDifferentialLendingRate, defaultDifferentialLendingRate, maxDifferentialLendingRate, isFloatingInterestRateCalculationAllowed, isVariableIntallmentsAllowed, minimumGap, - maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization); + maximumGap, syncExpectedWithDisbursementDate, canUseForTopup, isEqualAmortization, rateOptions, this.rates, isRatesEnabled); } } @@ -534,7 +544,7 @@ public class LoanProductReadPlatformServiceImpl implements LoanProductReadPlatfo public Collection retrieveAllLoanProductsForCurrency(String currencyCode) { this.context.authenticatedUser(); - final LoanProductMapper rm = new LoanProductMapper(null, null); + final LoanProductMapper rm = new LoanProductMapper(null, null, null); String sql = "select " + rm.loanProductSchema() + " where lp.currency_code= ? "; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java index c37087b32..17e00a69a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanProductWritePlatformServiceJpaRepositoryImpl.java @@ -49,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionProcessingStrategyRepository; import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingStrategyNotFoundException; import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator; +import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository; import org.apache.fineract.portfolio.loanproduct.domain.LoanTransactionProcessingStrategy; @@ -57,6 +58,8 @@ import org.apache.fineract.portfolio.loanproduct.exception.LoanProductCannotBeMo import org.apache.fineract.portfolio.loanproduct.exception.LoanProductDateException; import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException; import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator; +import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; import org.joda.time.LocalDate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,6 +79,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro private final FundRepository fundRepository; private final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository; private final ChargeRepositoryWrapper chargeRepository; + private final RateRepositoryWrapper rateRepository; private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService; private final FineractEntityAccessUtil fineractEntityAccessUtil; private final FloatingRateRepositoryWrapper floatingRateRepository; @@ -87,7 +91,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final LoanProductDataValidator fromApiJsonDeserializer, final LoanProductRepository loanProductRepository, final AprCalculator aprCalculator, final FundRepository fundRepository, final LoanTransactionProcessingStrategyRepository loanTransactionProcessingStrategyRepository, - final ChargeRepositoryWrapper chargeRepository, + final ChargeRepositoryWrapper chargeRepository, final RateRepositoryWrapper rateRepository, final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService, final FineractEntityAccessUtil fineractEntityAccessUtil, final FloatingRateRepositoryWrapper floatingRateRepository, @@ -100,6 +104,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro this.fundRepository = fundRepository; this.loanTransactionProcessingStrategyRepository = loanTransactionProcessingStrategyRepository; this.chargeRepository = chargeRepository; + this.rateRepository = rateRepository; this.accountMappingWritePlatformService = accountMappingWritePlatformService; this.fineractEntityAccessUtil = fineractEntityAccessUtil; this.floatingRateRepository = floatingRateRepository; @@ -125,6 +130,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro final String currencyCode = command.stringValueOfParameterNamed("currencyCode"); final List charges = assembleListOfProductCharges(command, currencyCode); + final List rates = assembleListOfProductRates(command); FloatingRate floatingRate = null; if(command.parameterExists("floatingRatesId")){ @@ -132,7 +138,7 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro .findOneWithNotFoundDetection(command.longValueOfParameterNamed("floatingRatesId")); } final LoanProduct loanproduct = LoanProduct.assembleFromJson(fund, loanTransactionProcessingStrategy, charges, command, - this.aprCalculator, floatingRate); + this.aprCalculator, floatingRate, rates); loanproduct.updateLoanProductInRelatedClasses(); this.loanProductRepository.save(loanproduct); @@ -237,6 +243,14 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro .updateLoanProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType()); changes.putAll(accountingMappingChanges); + if (changes.containsKey(LoanProductConstants.ratesParamName)) { + final List productRates = assembleListOfProductRates(command); + final boolean updated = product.updateRates(productRates); + if (!updated) { + changes.remove(LoanProductConstants.ratesParamName); + } + } + if (!changes.isEmpty()) { this.loanProductRepository.saveAndFlush(product); } @@ -299,6 +313,28 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro return charges; } + private List assembleListOfProductRates(final JsonCommand command) { + + final List rates = new ArrayList<>(); + + if (command.parameterExists("rates")) { + final JsonArray ratesArray = command.arrayOfParameterNamed("rates"); + if (ratesArray != null) { + List idList = new ArrayList<>(); + for (int i = 0; i < ratesArray.size(); i++) { + + final JsonObject jsonObject = ratesArray.get(i).getAsJsonObject(); + if (jsonObject.has("id")) { + idList.add(jsonObject.get("id").getAsLong()); + } + } + rates.addAll(this.rateRepository.findMultipleWithNotFoundDetection(idList)); + } + } + + return rates; + } + /* * Guaranteed to throw an exception no matter what the data integrity issue * is. diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java new file mode 100644 index 000000000..20d64d651 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiConstants.java @@ -0,0 +1,29 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.api; + +public class RateApiConstants { + + public static final String approveUserIdParamName = "approveUserId"; + public static final String rate = "rate"; + public static final String rateName = "name"; + public static final String ratePercentage = "percentage"; + public static final String rateProductApply = "productApply"; + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java new file mode 100644 index 000000000..801583163 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/api/RateApiResource.java @@ -0,0 +1,137 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.rate.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.rate.data.RateData; +import org.apache.fineract.portfolio.rate.service.RateReadService; +import org.apache.fineract.portfolio.rate.service.RateWriteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +import javax.ws.rs.*; +import javax.ws.rs.core.MediaType; +import java.util.Collection; + +/** + * Bowpi GT Created by Jose on 19/07/2017. + */ + +@Path("/rates") +@Component +@Scope("singleton") +public class RateApiResource { + + private final Set RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList("id", "name", "percentage", "productApply", "active")); + private final String resourceNameForPermissions = "RATE"; + private final PlatformSecurityContext context; + private final RateReadService readPlatformService; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + + @Autowired + public RateApiResource(final PlatformSecurityContext context, + final RateReadService rateReadService, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final ApiRequestParameterHelper apiRequestParameterHelper, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService) { + this.context = context; + this.readPlatformService = rateReadService; + this.toApiJsonSerializer = toApiJsonSerializer; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + } + + @GET + @Path("{rateId}") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public String retrieveRate(@PathParam("rateId") Long rateId, @Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + final RateData rate = this.readPlatformService.retrieveOne(rateId); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + + return this.toApiJsonSerializer.serialize(settings, rate, this.RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public String createRate(final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().createRate() + .withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + + } + + @GET + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public String getAllRates(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions); + + Collection rates = this.readPlatformService.retrieveAllRates(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + + return this.toApiJsonSerializer.serialize(settings, rates, this.RESPONSE_DATA_PARAMETERS); + } + + @PUT + @Path("{rateId}") + @Consumes({MediaType.APPLICATION_JSON}) + @Produces({MediaType.APPLICATION_JSON}) + public String updateRate(@PathParam("rateId") Long rateId, final String apiRequestBodyAsJson) { + final CommandWrapper commandRequest = new CommandWrapperBuilder().updateRate(rateId) + .withJson(apiRequestBodyAsJson).build(); + + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + + return this.toApiJsonSerializer.serialize(result); + } + + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java new file mode 100644 index 000000000..d60c8dd76 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/data/RateData.java @@ -0,0 +1,53 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.rate.data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * Bowpi GT Created by Jose on 19/07/2017. + */ +public class RateData implements Serializable { + + private Long id; + + private String name; + + private BigDecimal percentage; + + private String productApply; + + private boolean active; + + public static RateData instance(final Long id, final String name, final BigDecimal percentage, + final String productApply, final boolean active) { + return new RateData(id, name, percentage, productApply, active); + } + + private RateData(final Long id, final String name, final BigDecimal percentage, + final String productApply, final boolean active) { + this.id = id; + this.name = name; + this.percentage = percentage; + this.productApply = productApply; + this.active = active; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java new file mode 100644 index 000000000..3d0803b22 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/Rate.java @@ -0,0 +1,201 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.rate.domain; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.useradministration.domain.AppUser; + +import javax.persistence.*; +import java.math.BigDecimal; +import org.apache.fineract.infrastructure.core.domain.AbstractAuditableCustom; + +/** + * Bowpi GT Created by Jose on 19/07/2017. + */ + +@Entity +@Table(name = "m_rate", uniqueConstraints = { + @UniqueConstraint(columnNames = {"name"}, name = "name")}) +public class Rate extends AbstractAuditableCustom { + + @Column(name = "name", length = 250, unique = true) + private String name; + + @Column(name = "percentage", scale = 10, precision = 2, nullable = false) + private BigDecimal percentage; + + @Column(name = "product_apply", length = 100) + private String productApply; + + @Column(name = "active", nullable = false) + private boolean active; + + @ManyToOne + @JoinColumn(name = "approve_user", nullable = true) + private AppUser approveUser; + + + public Rate() { + } + + + public Rate(String name, BigDecimal percentage, String productApply, boolean active, + AppUser approveUser) { + this.name = name; + this.percentage = percentage; + this.productApply = productApply; + this.active = active; + this.approveUser = approveUser; + } + + public Rate(String name, BigDecimal percentage, String productApply, boolean active) { + this.name = name; + this.percentage = percentage; + this.productApply = productApply; + this.active = active; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public BigDecimal getPercentage() { + return percentage; + } + + public void setPercentage(BigDecimal percentage) { + this.percentage = percentage; + } + + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public AppUser getApproveUser() { + return approveUser; + } + + public void setApproveUser(AppUser approveUser) { + this.approveUser = approveUser; + } + + public String getProductApply() { + return productApply; + } + + public void setProductApply(String productApply) { + this.productApply = productApply; + } + + @Override + public String toString() { + return "Rate{" + + "name='" + name + '\'' + + ", percentage=" + percentage + + ", productApply='" + productApply + '\'' + + ", active=" + active + + ", approveUser=" + approveUser + + '}'; + } + + public static Rate from(String name, BigDecimal percentage, String productApply, Boolean active) { + return new Rate(name, percentage, productApply, active); + } + + public static Rate fromJson(final JsonCommand command, AppUser user) { + + final String name = command.stringValueOfParameterNamed("name"); + + final BigDecimal percentage = command.bigDecimalValueOfParameterNamed("percentage"); + + final String productApply = command.stringValueOfParameterNamed("productApply"); + + final boolean active = command.booleanPrimitiveValueOfParameterNamed("active"); + + return new Rate(name, percentage, productApply, active, user); + } + + public Map update(final JsonCommand command) { + + final Map actualChanges = new LinkedHashMap<>(7); + + final String nameParamName = "name"; + if (command.isChangeInStringParameterNamed(nameParamName, this.name)) { + final String newValue = command.stringValueOfParameterNamed(nameParamName); + actualChanges.put(nameParamName, newValue); + this.name = StringUtils.defaultIfEmpty(newValue, null); + } + + final String percentageParamName = "percentage"; + if (command.isChangeInBigDecimalParameterNamed(percentageParamName, this.percentage)) { + final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(percentageParamName); + actualChanges.put(percentageParamName, newValue); + this.percentage = newValue; + } + + final String productApplyParamName = "productApply"; + if (command.isChangeInStringParameterNamed(productApplyParamName, this.productApply)) { + final String newValue = command.stringValueOfParameterNamed(productApplyParamName); + actualChanges.put(productApplyParamName, newValue); + this.productApply = StringUtils.defaultIfEmpty(newValue, null); + } + + final String activeParamName = "active"; + if (command.isChangeInBooleanParameterNamed(activeParamName, this.active)) { + final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(activeParamName); + actualChanges.put(activeParamName, newValue); + this.active = newValue; + } + + final String approveUserParamName = "approveUserId"; + if (command.isChangeInLongParameterNamed(approveUserParamName, getApproveUserId())) { + final Long newValue = command.longValueOfParameterNamed(approveUserParamName); + actualChanges.put(approveUserParamName, newValue); + } + + return actualChanges; + } + + private Long getApproveUserId() { + Long approveUserId = null; + if (this.approveUser != null) { + approveUserId = this.approveUser.getId(); + } + return approveUserId; + } + + public void assembleFrom(String name, BigDecimal percentage, String productApply, boolean active){ + this.name = name; + this.percentage = percentage; + this.productApply = productApply; + this.active = active; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java new file mode 100644 index 000000000..e593ac82a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepository.java @@ -0,0 +1,36 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +import java.util.List; + +/** + * Bowpi GT + * Rate repository to save on m_rate table (custom change for Credi Chapin) + * Created by Jose on 19/07/2017. + */ +public interface RateRepository extends JpaRepository, JpaSpecificationExecutor { + + Rate findByName(String name); + List findAllByActiveAndProductApply(boolean active, String productApply); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java new file mode 100644 index 000000000..7604a1156 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/domain/RateRepositoryWrapper.java @@ -0,0 +1,70 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import org.apache.fineract.portfolio.rate.exception.RateNotFoundException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RateRepositoryWrapper { + + private final RateRepository repository; + + @Autowired + public RateRepositoryWrapper(final RateRepository repository) { + this.repository = repository; + } + + public Rate findOneWithNotFoundDetection(final Long rateId) { + + final Rate rate = this.repository.findOne(rateId); + if (rate == null) { + throw new RateNotFoundException(rateId); + } + + return rate; + } + + public List findMultipleWithNotFoundDetection(final List rateIds) { + List rates = new ArrayList<>(); + if (rateIds != null && !rateIds.isEmpty()) { + final List foundRates = this.repository.findAll(rateIds); + for (Long rateId : rateIds) { + Boolean found = false; + for (Rate foundRate : foundRates) { + if (Objects.equals(foundRate.getId(), + rateId)) { + found = true; + break; + } + } + if (!found) { + throw new RateNotFoundException(rateId); + } + } + rates.addAll(foundRates); + } + return rates; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java new file mode 100644 index 000000000..f0a844e88 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateAlreadyExistException.java @@ -0,0 +1,30 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.exception; + +/** + * Bowpi GT + * Created by Jose on 24/07/2017. + */ +public class RateAlreadyExistException extends Exception{ + + public RateAlreadyExistException(String s) { + super(s); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java new file mode 100644 index 000000000..48cf904fb --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/exception/RateNotFoundException.java @@ -0,0 +1,35 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.rate.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException; + +public class RateNotFoundException extends AbstractPlatformResourceNotFoundException { + + + public RateNotFoundException(final Long id) { + super("error.msg.rate.id.invalid", "Rate with identifier " + id + " does not exist", id); + } + + public RateNotFoundException(final String name) { + super("error.msg.rate.id.invalid", "Rate with name " + name + " does not exist", name); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java new file mode 100644 index 000000000..879454047 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/CreateRateCommandHandler.java @@ -0,0 +1,48 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.rate.service.RateWriteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * Bowpi GT + * Created by Jose on 19/07/2017. + */ +@Service +@CommandType(entity = "RATE", action = "CREATE") +public class CreateRateCommandHandler implements NewCommandSourceHandler { + + private final RateWriteService writePlatformService; + + @Autowired + public CreateRateCommandHandler(final RateWriteService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.createRate(command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java new file mode 100644 index 000000000..cc22e53dc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/handler/UpdateRateCommandHandler.java @@ -0,0 +1,51 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.rate.service.RateWriteService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Bowpi GT + * Created by Jose on 19/07/2017. + */ +@Service +@CommandType(entity = "RATE", action = "UPDATE") +public class UpdateRateCommandHandler implements NewCommandSourceHandler { + + private final RateWriteService writePlatformService; + + @Autowired + public UpdateRateCommandHandler(final RateWriteService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Transactional + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + + return this.writePlatformService.updateRate(command.entityId(), command); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java new file mode 100644 index 000000000..acfbc72bf --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/serialization/RateDefinitionCommandFromApiJsonDeserializer.java @@ -0,0 +1,129 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.serialization; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; +import java.lang.reflect.Type; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang.StringUtils; +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.rate.api.RateApiConstants; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class RateDefinitionCommandFromApiJsonDeserializer { + + /** + * The parameters supported for this command. + */ + private final Set supportedParameters = new HashSet<>( + Arrays.asList("id", "name", "percentage", "productApply", "active", "approveUser", "locale")); + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public RateDefinitionCommandFromApiJsonDeserializer(final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public void validateForCreate(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() { + }.getType(); + + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters); + + final List dataValidationErrors = new ArrayList<>(); + + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RateApiConstants.rateName); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String name = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateName, element); + baseDataValidator.reset().parameter(RateApiConstants.rateName).value(name).notBlank().notExceedingLengthOf(250); + + final BigDecimal percentage = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(RateApiConstants.ratePercentage, element); + baseDataValidator.reset().parameter(RateApiConstants.ratePercentage).value(percentage).notBlank(); + + final String productApply = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateProductApply, element); + baseDataValidator.reset().parameter(RateApiConstants.rateProductApply).value(productApply).notBlank() + .notExceedingLengthOf(100); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + public void validateForUpdate(final String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) + .resource(RateApiConstants.rate); + + final JsonElement element = this.fromApiJsonHelper.parse(json); + + if (this.fromApiJsonHelper.parameterExists(RateApiConstants.rateName, element)) { + final String name = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateName, element); + baseDataValidator.reset().parameter(RateApiConstants.rateName).value(name).notBlank().notExceedingLengthOf(250); + } + + if (this.fromApiJsonHelper.parameterExists(RateApiConstants.ratePercentage, element)) { + final BigDecimal percentage = this.fromApiJsonHelper + .extractBigDecimalWithLocaleNamed(RateApiConstants.ratePercentage, element); + baseDataValidator.reset().parameter(RateApiConstants.ratePercentage).value(percentage).notBlank(); + } + + if (this.fromApiJsonHelper.parameterExists(RateApiConstants.rateProductApply, element)) { + final String productApply = this.fromApiJsonHelper.extractStringNamed(RateApiConstants.rateProductApply, element); + baseDataValidator.reset().parameter(RateApiConstants.rateProductApply).value(productApply).notBlank(); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + } + + private void throwExceptionIfValidationWarningsExist( + final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java new file mode 100644 index 000000000..9b2d6f9a6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateAssembler.java @@ -0,0 +1,80 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.service; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.loanproduct.LoanProductConstants; +import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.rate.domain.RateRepositoryWrapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RateAssembler { + + private final FromJsonHelper fromApiJsonHelper; + private final RateRepositoryWrapper rateRepository; + + @Autowired + public RateAssembler(final FromJsonHelper fromApiJsonHelper, + final RateRepositoryWrapper rateRepository) { + this.fromApiJsonHelper = fromApiJsonHelper; + this.rateRepository = rateRepository; + } + + public List fromParsedJson(final JsonElement element) { + + final List rateItems = new ArrayList<>(); + + if (element.isJsonObject()) { + final JsonObject topLevelJsonElement = element.getAsJsonObject(); + final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement); + + if (topLevelJsonElement.has(LoanProductConstants.ratesParamName) && topLevelJsonElement + .get(LoanProductConstants.ratesParamName) + .isJsonArray()) { + final JsonArray array = topLevelJsonElement.get(LoanProductConstants.ratesParamName) + .getAsJsonArray(); + List idList = new ArrayList<>(); + + for (int i = 0; i < array.size(); i++) { + + final JsonObject rateElement = array.get(i).getAsJsonObject(); + + final Long id = this.fromApiJsonHelper.extractLongNamed("id", rateElement); + + if (id != null) { + final Long rateId = this.fromApiJsonHelper.extractLongNamed("id", rateElement); + idList.add(rateId); + } + } + rateItems.addAll(rateRepository.findMultipleWithNotFoundDetection(idList)); + } + } + + return rateItems; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java new file mode 100644 index 000000000..7b4b05121 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadService.java @@ -0,0 +1,44 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.service; + +import org.apache.fineract.portfolio.rate.data.RateData; + +import java.util.Collection; +import java.util.List; + +/** + * Bowpi GT + * Created by Jose on 19/07/2017. + */ +public interface RateReadService { + + Collection retrieveAllRates(); + + Collection retrieveLoanApplicableRates(); + + RateData retrieveOne(Long rateId); + + RateData retrieveByName(String name); + + List retrieveProductLoanRates(Long loanId); + + List retrieveLoanRates(Long loanId); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java new file mode 100644 index 000000000..44682ded6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateReadServiceImpl.java @@ -0,0 +1,150 @@ +/** + * 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. + */ + +package org.apache.fineract.portfolio.rate.service; + +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.rate.exception.RateNotFoundException; +import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.rate.data.RateData; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.List; + +/** + * Bowpi GT Created by Jose on 19/07/2017. + */ +@Service +public class RateReadServiceImpl implements RateReadService { + + private final JdbcTemplate jdbcTemplate; + private final PlatformSecurityContext context; + + @Autowired + public RateReadServiceImpl(PlatformSecurityContext context, final RoutingDataSource dataSource) { + this.context = context; + this.jdbcTemplate = new JdbcTemplate(dataSource); + } + + @Override + public Collection retrieveAllRates() { + this.context.authenticatedUser(); + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.rateSchema(); + return this.jdbcTemplate.query(sql, rm, new Object[]{}); + } + + @Override + public RateData retrieveOne(Long rateId) { + try { + this.context.authenticatedUser(); + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.rateSchema() + " where r.id = ?"; + final RateData selectedRate = this.jdbcTemplate.queryForObject(sql, rm, new Object[]{rateId}); + return selectedRate; + + } catch (final EmptyResultDataAccessException e) { + throw new RateNotFoundException(rateId); + } + } + + @Override + public RateData retrieveByName(String name) { + try { + this.context.authenticatedUser(); + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.rateSchema() + " where r.name = ?"; + final RateData selectedRate = this.jdbcTemplate.queryForObject(sql, rm, new Object[]{name}); + return selectedRate; + + } catch (final EmptyResultDataAccessException e) { + throw new RateNotFoundException(name); + } + } + + @Override + public Collection retrieveLoanApplicableRates() { + this.context.authenticatedUser(); + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.rateSchema() + " where r.active = ? and product_apply=?"; + return this.jdbcTemplate.query(sql, rm, new Object[]{true, "m_loan"}); + } + + @Override + public List retrieveLoanRates(Long loanId) { + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.loanRateSchema() + " where lr.loan_id = ?"; + return this.jdbcTemplate.query(sql, rm, new Object[]{loanId}); + } + + @Override + public List retrieveProductLoanRates(Long loanId) { + final RateMapper rm = new RateMapper(); + final String sql = "select " + rm.productLoanRateSchema() + " where lr.product_loan_id = ?"; + return this.jdbcTemplate.query(sql, rm, new Object[]{loanId}); + } + + private static final class RateMapper implements RowMapper { + + + public String rateSchema() { + return " r.id as id, r.name as name, r.percentage as percentage, " + + "r.product_apply as productApply, r.active as active from m_rate r "; + } + + public String loanRateSchema() { + return rateSchema() + " join m_loan_rate lr on lr.rate_id = r.id"; + } + + public String productLoanRateSchema() { + return rateSchema() + " join m_product_loan_rate lr on lr.rate_id = r.id"; + } + + public RateMapper() { + } + + @Override + public RateData mapRow(ResultSet resultSet, int i) throws SQLException { + final Long id = resultSet.getLong("id"); + final String name = resultSet.getString("name"); + final BigDecimal percentage = resultSet.getBigDecimal("percentage"); + final String productApply = resultSet.getString("productApply"); + final boolean active = resultSet.getBoolean("active"); + return RateData.instance(id, name, percentage, productApply, active); + } + + public RateData mapRow(Rate rateResponse, int i) { + final Long id = rateResponse.getId(); + final String name = rateResponse.getName(); + final BigDecimal percentage = rateResponse.getPercentage(); + final String productApply = rateResponse.getProductApply(); + final boolean active = rateResponse.isActive(); + return RateData.instance(id, name, percentage, productApply, active); + } + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java new file mode 100644 index 000000000..0cd7dfea5 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteService.java @@ -0,0 +1,34 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +/** + * Bowpi GT + * Created by Jose on 19/07/2017. + */ +public interface RateWriteService { + + CommandProcessingResult createRate(final JsonCommand command); + + CommandProcessingResult updateRate(Long rateId, JsonCommand command); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java new file mode 100644 index 000000000..51f97b4e8 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/rate/service/RateWriteServiceImpl.java @@ -0,0 +1,157 @@ +/** + * 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. + */ +package org.apache.fineract.portfolio.rate.service; + +import static org.apache.fineract.portfolio.rate.api.RateApiConstants.approveUserIdParamName; + +import java.util.Map; +import javax.persistence.PersistenceException; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.rate.domain.Rate; +import org.apache.fineract.portfolio.rate.domain.RateRepository; +import org.apache.fineract.portfolio.rate.exception.RateNotFoundException; +import org.apache.fineract.portfolio.rate.serialization.RateDefinitionCommandFromApiJsonDeserializer; +import org.apache.fineract.useradministration.domain.AppUser; +import org.apache.fineract.useradministration.domain.AppUserRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * Bowpi GT Created by Jose on 19/07/2017. + */ +@Service +public class RateWriteServiceImpl implements RateWriteService { + + private final static Logger logger = LoggerFactory + .getLogger(RateWriteServiceImpl.class); + private final RateRepository rateRepository; + private final AppUserRepository appUserRepository; + private final PlatformSecurityContext context; + private final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer; + + @Autowired + public RateWriteServiceImpl(RateRepository rateRepository, AppUserRepository appUserRepository, + final RateDefinitionCommandFromApiJsonDeserializer fromApiJsonDeserializer, + PlatformSecurityContext context) { + this.rateRepository = rateRepository; + this.appUserRepository = appUserRepository; + this.context = context; + this.fromApiJsonDeserializer = fromApiJsonDeserializer; + } + + @Override + public CommandProcessingResult createRate(JsonCommand command) { + try { + this.context.authenticatedUser(); + this.fromApiJsonDeserializer.validateForCreate(command.json()); + + final Long approveUserId = command.longValueOfParameterNamed(approveUserIdParamName); + AppUser approveUser = null; + if (approveUserId != null) { + approveUser = this.appUserRepository.findOne(approveUserId); + } + final Rate rate = Rate.fromJson(command, approveUser); + + this.rateRepository.save(rate); + + return new CommandProcessingResultBuilder().withCommandId(command.commandId()) + .withEntityId(rate.getId()).build(); + + } catch (final DataIntegrityViolationException dve) { + handleRateDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); + return CommandProcessingResult.empty(); + } catch (final PersistenceException dve) { + Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); + handleRateDataIntegrityIssues(command, throwable, dve); + return CommandProcessingResult.empty(); + } + } + + @Transactional + @Override + public CommandProcessingResult updateRate(final Long rateId, final JsonCommand command) { + try { + this.context.authenticatedUser(); + + final Rate rateToUpdate = this.rateRepository.findOne(rateId); + if (rateToUpdate == null) { + throw new RateNotFoundException(rateId); + } + + final Map changes = rateToUpdate.update(command); + + this.fromApiJsonDeserializer.validateForUpdate(command.json()); + + if (changes.containsKey(approveUserIdParamName)) { + final Long newValue = (Long) changes.get(approveUserIdParamName); + AppUser newApproveUser = null; + if (newValue != null) { + newApproveUser = this.appUserRepository.findOne(newValue); + } + rateToUpdate.setApproveUser(newApproveUser); + } + if (!changes.isEmpty()) { + this.rateRepository.saveAndFlush(rateToUpdate); + } + + return new CommandProcessingResultBuilder() // + .withCommandId(command.commandId()) // + .withEntityId(rateId) // + .with(changes) // + .build(); + + } catch (final DataIntegrityViolationException dve) { + handleRateDataIntegrityIssues(command, dve.getMostSpecificCause(), dve); + return new CommandProcessingResult(Long.valueOf(-1)); + } catch (final PersistenceException dve) { + Throwable throwable = ExceptionUtils.getRootCause(dve.getCause()); + handleRateDataIntegrityIssues(command, throwable, dve); + return CommandProcessingResult.empty(); + } + + } + + /* + * Guaranteed to throw an exception no matter what the data integrity issue + * is. + */ + private void handleRateDataIntegrityIssues(final JsonCommand command, final Throwable realCause, + final Exception dve) { + if (realCause.getMessage().contains("rate_name_org")) { + final String name = command.stringValueOfParameterNamed("name"); + throw new PlatformDataIntegrityException("error.msg.fund.duplicate.externalId", + "A rate with name '" + name + + "' already exists", "name", name); + } + + logger.error(dve.getMessage(), dve); + throw new PlatformDataIntegrityException("error.msg.fund.unknown.data.integrity.issue", + "Unknown data integrity issue with resource: " + realCause.getMessage()); + } + +} diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql new file mode 100644 index 000000000..1ca098656 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V352__rates.sql @@ -0,0 +1,67 @@ +-- +-- 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. +-- + +CREATE TABLE IF NOT EXISTS `m_rate` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(250) NOT NULL, + `percentage` decimal(10,2) NOT NULL, + `active` tinyint(1) DEFAULT '0', + `product_apply` varchar(100) NOT NULL, + `created_date` datetime NULL DEFAULT NULL, + `createdby_id` bigint(20) NOT NULL, + `lastmodifiedby_id` bigint(20) NOT NULL, + `lastmodified_date` datetime NULL DEFAULT NULL, + `approve_user` bigint(20) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `FK_M_RATE_CREATE_USER` (`createdby_id`), + KEY `FK_M_RATE_APPROVE_USER` (`approve_user`), + CONSTRAINT `FK_M_RATE_APPROVE_USER` FOREIGN KEY (`approve_user`) REFERENCES `m_appuser` (`id`), + CONSTRAINT `FK_M_RATE_CREATE_USER` FOREIGN KEY (`createdby_id`) REFERENCES `m_appuser` (`id`) +); + + +CREATE TABLE IF NOT EXISTS `m_loan_rate` ( + `loan_id` bigint(20) NOT NULL, + `rate_id` bigint(20) NOT NULL, + PRIMARY KEY (`loan_id`,`rate_id`), + KEY `FK_M_LOAN_RATE_RATE` (`rate_id`), + CONSTRAINT `FK_M_LOAN_RATE_LOAN` FOREIGN KEY (`loan_id`) REFERENCES `m_loan` (`id`), + CONSTRAINT `FK_M_LOAN_RATE_RATE` FOREIGN KEY (`rate_id`) REFERENCES `m_rate` (`id`) +); + + +CREATE TABLE IF NOT EXISTS `m_product_loan_rate` ( + `product_loan_id` bigint(20) NOT NULL, + `rate_id` bigint(20) NOT NULL, + PRIMARY KEY (`product_loan_id`,`rate_id`), + KEY `FK_M_PRODUCT_LOAN_RATE_RATE` (`rate_id`), + CONSTRAINT `FK_M_PRODUCT_LOAN_RATE_LOAN` FOREIGN KEY (`product_loan_id`) REFERENCES `m_product_loan` (`id`), + CONSTRAINT `FK_M_PRODUCT_LOAN_RATE_RATE` FOREIGN KEY (`rate_id`) REFERENCES `m_rate` (`id`) +); + + +INSERT INTO `m_permission` +(`grouping`,`code`,`entity_name`,`action_name`,`can_maker_checker`) VALUES + ('organisation', 'CREATE_RATE', 'RATE', 'CREATE', '1'), + ('organisation', 'UPDATE_RATE', 'RATE', 'UPDATE', '1'); + +INSERT INTO `c_configuration` +(`name`, `value`, `enabled`, `is_trap_door`, `description`) VALUES +('vat-tax', 12, 0, 0, 'VAT tax'), +('sub-rates', 12, 0, 0, 'Enable Rates Module');;