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.

This commit is contained in:
Angel Cajas
2020-03-13 17:15:25 +01:00
committed by Michael Vorburger ⛑️
parent f484f884a0
commit a7d4c83e92
41 changed files with 1841 additions and 83 deletions
@@ -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;
}
}
@@ -92,4 +92,6 @@ public interface ConfigurationDomainService {
Integer retrieveOTPCharacterLength();
Integer retrieveOTPLiveTime();
boolean isSubRatesEnabled();
}
@@ -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();
}
}
}
@@ -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<LoanTransactionData> 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<LoanTransactionData> 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<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -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<String> 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<LoanAccountData> toApiJsonSerializer,
final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
final DefaultToApiJsonSerializer<LoanScheduleData> 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<LoanAccountData> toApiJsonSerializer,
final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
final DefaultToApiJsonSerializer<LoanScheduleData> 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<PortfolioAccountData> 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<DatatableData> 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<RateData> 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);
@@ -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<DatatableData> datatables = null;
private final Boolean isEqualAmortization;
//Rate
private final List<RateData> 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<RateData> 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<RateData> 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<RateData> 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<RateData> 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<InterestRatePeriodData> interestRatesPeriods = null;
final Collection<LoanAccountSummaryData> clientActiveLoanOptions = null;
final List<RateData> 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<DisbursementData> disbursementDetails,
final Collection<LoanTermVariationsData> emiAmountVariations, final Collection<ChargeData> overdueCharges,
final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions) {
final Collection<LoanAccountSummaryData> clientActiveLoanOptions,final List<RateData> 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<LoanProductData> productOptions,
final Collection<StaffData> allowedLoanOfficers, final Collection<CalendarData> calendarOptions,
final Collection<PortfolioAccountData> accountLinkingOptions) {
final Collection<PortfolioAccountData> 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<Long, Integer> 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<InterestRatePeriodData> interestRatesPeriods, final Boolean isVariableInstallmentsAllowed,
final Integer minimumGap, final Integer maximumGap, final EnumOptionData subStatus, final Boolean canUseForTopup,
final Collection<LoanAccountSummaryData> 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<RateData> 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;
}
@@ -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;
}
}
@@ -59,6 +59,8 @@ public class LoanTransactionData {
private final boolean manuallyReversed;
private final LocalDate possibleNextRepaymentDate;
private Collection<LoanChargePaidByData> loanChargePaidByList;
// templates
final Collection<PaymentTypeData> paymentTypeOptions;
@@ -339,4 +341,12 @@ public class LoanTransactionData {
public void setWriteOffReasonOptions(Collection<CodeValueData> writeOffReasonOptions){
this.writeOffReasonOptions =writeOffReasonOptions;
}
public Collection<LoanChargePaidByData> getLoanChargePaidByList() {
return loanChargePaidByList;
}
public void setLoanChargePaidByList(Collection<LoanChargePaidByData> loanChargePaidByList) {
this.loanChargePaidByList = loanChargePaidByList;
}
}
@@ -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<Long> {
@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<Rate> 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<LoanCharge> loanCharges,
final Set<LoanCollateral> collateral, final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> 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<Long> {
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential) {
final BigDecimal interestRateDifferential, final List<Rate> 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<Long> {
final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting, final BigDecimal fixedEmiAmount,
final List<LoanDisbursementDetails> disbursementDetails, final BigDecimal maxOutstandingLoanBalance,
final Boolean createStandingInstructionAtDisbursement, final Boolean isFloatingInterestRate,
final BigDecimal interestRateDifferential) {
final BigDecimal interestRateDifferential, final List<Rate> 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<Long> {
final Set<LoanCharge> loanCharges, final Set<LoanCollateral> collateral, final Boolean syncDisbursementWithMeeting,
final BigDecimal fixedEmiAmount, final List<LoanDisbursementDetails> disbursementDetails,
final BigDecimal maxOutstandingLoanBalance, final Boolean createStandingInstructionAtDisbursement,
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential) {
final Boolean isFloatingInterestRate, final BigDecimal interestRateDifferential, final List<Rate> rates) {
this.loanRepaymentScheduleDetail = loanRepaymentScheduleDetail;
this.loanRepaymentScheduleDetail.validateRepaymentPeriodWithGraceSettings();
@@ -503,6 +508,9 @@ public class Loan extends AbstractPersistableCustom<Long> {
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<Long> {
this.collateral.addAll(associateWithThisLoan(loanCollateral));
}
public void updateLoanRates(final List<Rate> 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<Long> {
}
public boolean isIndividualLoan(){return AccountType.fromInt(this.loanType).isIndividualAccount();}
public void setRates(List<Rate> rates) {
this.rates = rates;
}
public List<Rate> getRates() {
return rates;
}
}
@@ -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;
@@ -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;
@@ -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(),
@@ -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<Rate> 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);
}
@@ -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<LoanChargePaidByData> getLoanChargesPaidByTransactionId(Long id);
}
@@ -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<LoanChargePaidByData> 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<LoanChargePaidByData> {
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);
}
}
}
@@ -134,5 +134,6 @@ public interface LoanProductConstants {
public static final String isEqualAmortizationParam = "isEqualAmortization";
public static final String ratesParamName = "rates";
}
@@ -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<String> 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<RateData> rateOptions = this.rateReadService.retrieveLoanApplicableRates();
if(rateOptions.isEmpty()){
rateOptions = null;
}
final Collection<CurrencyData> currencyOptions = this.currencyReadPlatformService.retrieveAllowedCurrencies();
final List<EnumOptionData> amortizationTypeOptions = this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions();
final List<EnumOptionData> 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);
}
}
@@ -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<ChargeToGLAccountMapper> feeToIncomeAccountMappings;
private Collection<ChargeToGLAccountMapper> penaltyToIncomeAccountMappings;
// rates
private final boolean isRatesEnabled;
private final Collection<RateData> rates;
// template related
private final Collection<FundData> fundOptions;
@SuppressWarnings("unused")
@@ -146,6 +151,7 @@ public class LoanProductData implements Serializable {
private final List<EnumOptionData> interestCalculationPeriodTypeOptions;
private final Collection<TransactionProcessingStrategyData> transactionProcessingStrategyOptions;
private final Collection<ChargeData> chargeOptions;
private final Collection<RateData> rateOptions;
@SuppressWarnings("unused")
private final Collection<ChargeData> 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<RateData> rateOptions = null;
final Collection<RateData> 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<RateData> rateOptions = null;
final Collection<RateData> 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<RateData> rateOptions = null;
final Collection<RateData> 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<RateData> rateOptions = null;
final Collection<RateData> 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<RateData> rateOptions, Collection<RateData> 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<EnumOptionData> interestTypeOptions, final List<EnumOptionData> interestCalculationPeriodTypeOptions,
final List<EnumOptionData> repaymentFrequencyTypeOptions, final List<EnumOptionData> interestRateFrequencyTypeOptions,
final Collection<FundData> fundOptions, final Collection<TransactionProcessingStrategyData> transactionStrategyOptions,
final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
final Collection<RateData> rateOptions, final Map<String, List<GLAccountData>> accountingMappingOptions, final List<EnumOptionData> accountingRuleOptions,
final List<EnumOptionData> valueConditionTypeOptions, final List<EnumOptionData> daysInMonthTypeOptions,
final List<EnumOptionData> daysInYearTypeOptions, final List<EnumOptionData> interestRecalculationCompoundingTypeOptions,
final List<EnumOptionData> rescheduleStrategyTypeOptions, final List<EnumOptionData> 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<TransactionProcessingStrategyData> 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<ChargeData> nullIfEmpty(final Collection<ChargeData> 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;
@@ -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<Long> {
@JoinTable(name = "m_product_loan_charge", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "charge_id"))
private List<Charge> charges;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "m_product_loan_rate", joinColumns = @JoinColumn(name = "product_loan_id"), inverseJoinColumns = @JoinColumn(name = "rate_id"))
private List<Rate> rates;
@Embedded
private LoanProductRelatedDetail loanProductRelatedDetail;
@@ -184,7 +189,8 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
private boolean isEqualAmortization = false;
public static LoanProduct assembleFromJson(final Fund fund, final LoanTransactionProcessingStrategy loanTransactionProcessingStrategy,
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate) {
final List<Charge> productCharges, final JsonCommand command, final AprCalculator aprCalculator, FloatingRate floatingRate,
final List<Rate> productRates) {
final String name = command.stringValueOfParameterNamed("name");
final String shortName = command.stringValueOfParameterNamed(LoanProductConstants.shortName);
@@ -350,7 +356,7 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
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<Long> {
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<Rate> rates) {
this.fund = fund;
this.transactionProcessingStrategy = transactionProcessingStrategy;
this.name = name.trim();
@@ -658,6 +664,10 @@ public class LoanProduct extends AbstractPersistableCustom<Long> {
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<Long> {
return updated;
}
public boolean updateRates(final List<Rate> newProductRates) {
if (newProductRates == null) { return false; }
boolean updated = false;
if (this.rates != null) {
final Set<Rate> currentSetOfCharges = new HashSet<>(this.rates);
final Set<Rate> 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<Long> {
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<Long> {
this.isEqualAmortization = isEqualAmortization;
}
public List<Rate> getRates() {
return rates;
}
public void setRates(List<Rate> rates) {
this.rates = rates;
}
}
@@ -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;
}
}
@@ -666,4 +666,7 @@ public class LoanProductRelatedDetail implements LoanProductMinimumRepaymentSche
this.isEqualAmortization = isEqualAmortization;
}
public void setNominalInterestRatePerPeriod(BigDecimal nominalInterestRatePerPeriod) {
this.nominalInterestRatePerPeriod = nominalInterestRatePerPeriod;
}
}
@@ -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,
@@ -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<ChargeData> charges = this.chargeReadPlatformService.retrieveLoanProductCharges(loanProductId);
final Collection<RateData> rates = this.rateReadService.retrieveProductLoanRates(loanProductId);
final Collection<LoanProductBorrowerCycleVariationData> 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<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas;
private final Collection<RateData> rates;
public LoanProductMapper(final Collection<ChargeData> charges,
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas) {
final Collection<LoanProductBorrowerCycleVariationData> borrowerCycleVariationDatas, final Collection<RateData> 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<RateData> 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<LoanProductData> 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= ? ";
@@ -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<Charge> charges = assembleListOfProductCharges(command, currencyCode);
final List<Rate> 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<Rate> 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<Rate> assembleListOfProductRates(final JsonCommand command) {
final List<Rate> rates = new ArrayList<>();
if (command.parameterExists("rates")) {
final JsonArray ratesArray = command.arrayOfParameterNamed("rates");
if (ratesArray != null) {
List<Long> 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.
@@ -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";
}
@@ -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<String> 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<RateData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
@Autowired
public RateApiResource(final PlatformSecurityContext context,
final RateReadService rateReadService,
final DefaultToApiJsonSerializer<RateData> 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<RateData> 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);
}
}
@@ -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;
}
}
@@ -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<AppUser, Long> {
@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<String, Object> update(final JsonCommand command) {
final Map<String, Object> 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;
}
}
@@ -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<Rate, Long>, JpaSpecificationExecutor<Rate> {
Rate findByName(String name);
List<Rate> findAllByActiveAndProductApply(boolean active, String productApply);
}
@@ -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<Rate> findMultipleWithNotFoundDetection(final List<Long> rateIds) {
List<Rate> rates = new ArrayList<>();
if (rateIds != null && !rateIds.isEmpty()) {
final List<Rate> 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;
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<String> 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<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
final List<ApiParameterError> 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<Map<String, Object>>() {
}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, this.supportedParameters);
final List<ApiParameterError> 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<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
}
@@ -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<Rate> fromParsedJson(final JsonElement element) {
final List<Rate> 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<Long> 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;
}
}
@@ -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<RateData> retrieveAllRates();
Collection<RateData> retrieveLoanApplicableRates();
RateData retrieveOne(Long rateId);
RateData retrieveByName(String name);
List<RateData> retrieveProductLoanRates(Long loanId);
List<RateData> retrieveLoanRates(Long loanId);
}
@@ -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<RateData> 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<RateData> 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<RateData> 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<RateData> 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<RateData> {
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);
}
}
}
@@ -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);
}
@@ -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<String, Object> 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());
}
}
@@ -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');;