FINERACT-1932: Fineract modularization - fineract-savings

This commit is contained in:
Jose Alberto Hernandez
2024-01-29 14:14:31 +01:00
committed by Adam Saghy
parent 1f00bb6d33
commit 02cf6749af
213 changed files with 352 additions and 78 deletions
@@ -1,241 +0,0 @@
/**
* 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.accounting.producttoaccountmapping.service;
import com.google.gson.JsonElement;
import java.util.HashMap;
import java.util.Map;
import org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepositoryWrapper;
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.PortfolioProductType;
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.paymenttype.domain.PaymentTypeRepositoryWrapper;
import org.springframework.stereotype.Component;
@Component
public class SavingsProductToGLAccountMappingHelper extends ProductToGLAccountMappingHelper {
public SavingsProductToGLAccountMappingHelper(final GLAccountRepository glAccountRepository,
final ProductToGLAccountMappingRepository glAccountMappingRepository, final FromJsonHelper fromApiJsonHelper,
final ChargeRepositoryWrapper chargeRepositoryWrapper, final GLAccountRepositoryWrapper accountRepositoryWrapper,
final PaymentTypeRepositoryWrapper paymentTypeRepositoryWrapper) {
super(glAccountRepository, glAccountMappingRepository, fromApiJsonHelper, chargeRepositoryWrapper, accountRepositoryWrapper,
paymentTypeRepositoryWrapper);
}
/***
* Set of abstractions for saving Saving Products to GL Account Mappings
***/
public void saveSavingsToAssetAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.ASSET, PortfolioProductType.SAVING);
}
public void saveSavingsToIncomeAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.INCOME, PortfolioProductType.SAVING);
}
public void saveSavingsToExpenseAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.EXPENSE, PortfolioProductType.SAVING);
}
public void saveSavingsToLiabilityAccountMapping(final JsonElement element, final String paramName, final Long productId,
final int placeHolderTypeId) {
saveProductToAccountMapping(element, paramName, productId, placeHolderTypeId, GLAccountType.LIABILITY, PortfolioProductType.SAVING);
}
/***
* Set of abstractions for merging Savings Products to GL Account Mappings
***/
public void mergeSavingsToAssetAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.ASSET,
PortfolioProductType.SAVING);
}
public void mergeSavingsToIncomeAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.INCOME,
PortfolioProductType.SAVING);
}
public void mergeSavingsToExpenseAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.EXPENSE,
PortfolioProductType.SAVING);
}
public void mergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName, final Long productId,
final int accountTypeId, final String accountTypeName, final Map<String, Object> changes) {
mergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, accountTypeName, changes, GLAccountType.LIABILITY,
PortfolioProductType.SAVING);
}
public void createOrmergeSavingsToLiabilityAccountMappingChanges(final JsonElement element, final String paramName,
final Long productId, final int accountTypeId, final Map<String, Object> changes) {
createOrmergeProductToAccountMappingChanges(element, paramName, productId, accountTypeId, changes, GLAccountType.LIABILITY,
PortfolioProductType.SAVING);
}
/*** Abstractions for payments channel related to savings products ***/
public void savePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
savePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
}
public void updatePaymentChannelToFundSourceMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
updatePaymentChannelToFundSourceMappings(command, element, productId, changes, PortfolioProductType.SAVING);
}
public void saveChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// save both fee and penalty charges
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
saveChargesToGLAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
}
public void updateChargesToIncomeAccountMappings(final JsonCommand command, final JsonElement element, final Long productId,
final Map<String, Object> changes) {
// update both fee and penalty charges
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, true);
updateChargeToIncomeAccountMappings(command, element, productId, changes, PortfolioProductType.SAVING, false);
}
public Map<String, Object> populateChangesForNewSavingsProductToGLAccountMappingCreation(final JsonElement element,
final AccountingRuleType accountingRuleType) {
final Map<String, Object> changes = new HashMap<>();
final Long savingsReferenceId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
element);
final Long incomeFromFeesId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
element);
final Long incomeFromPenaltiesId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), element);
final Long interestOnSavingsId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), element);
final Long savingsControlId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
element);
final Long transfersInSuspenseAccountId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), element);
final Long overdraftControlId = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), element);
final Long incomeFromInterest = this.fromApiJsonHelper
.extractLongNamed(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), element);
final Long writeOffId = this.fromApiJsonHelper.extractLongNamed(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
element);
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
changes.put(SavingProductAccountingParams.SAVINGS_CONTROL.getValue(), savingsControlId);
changes.put(SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(), savingsReferenceId);
changes.put(SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(), interestOnSavingsId);
changes.put(SavingProductAccountingParams.INCOME_FROM_FEES.getValue(), incomeFromFeesId);
changes.put(SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(), incomeFromPenaltiesId);
changes.put(SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(), transfersInSuspenseAccountId);
changes.put(SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(), overdraftControlId);
changes.put(SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(), incomeFromInterest);
changes.put(SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(), writeOffId);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
return changes;
}
/**
* Examines and updates each account mapping for given loan product with changes passed in from the Json element
*
* @param savingsProductId
* @param changes
* @param element
* @param accountingRuleType
*/
public void handleChangesToSavingsProductToGLAccountMappings(final Long savingsProductId, final Map<String, Object> changes,
final JsonElement element, final AccountingRuleType accountingRuleType) {
switch (accountingRuleType) {
case NONE:
break;
case CASH_BASED:
// asset
mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
savingsProductId, CashAccountsForSavings.SAVINGS_REFERENCE.getValue(),
CashAccountsForSavings.SAVINGS_REFERENCE.toString(), changes);
mergeSavingsToAssetAccountMappingChanges(element, SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
savingsProductId, CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
CashAccountsForSavings.OVERDRAFT_PORTFOLIO_CONTROL.toString(), changes);
// income
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_FEES.getValue(),
CashAccountsForSavings.INCOME_FROM_FEES.toString(), changes);
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_PENALTIES.getValue(),
CashAccountsForSavings.INCOME_FROM_PENALTIES.toString(), changes);
mergeSavingsToIncomeAccountMappingChanges(element, SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
savingsProductId, CashAccountsForSavings.INCOME_FROM_INTEREST.getValue(),
CashAccountsForSavings.INCOME_FROM_INTEREST.toString(), changes);
// expenses
mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
savingsProductId, CashAccountsForSavings.INTEREST_ON_SAVINGS.getValue(),
CashAccountsForSavings.INTEREST_ON_SAVINGS.toString(), changes);
mergeSavingsToExpenseAccountMappingChanges(element, SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
savingsProductId, CashAccountsForSavings.LOSSES_WRITTEN_OFF.getValue(),
CashAccountsForSavings.LOSSES_WRITTEN_OFF.toString(), changes);
// liability
mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
savingsProductId, CashAccountsForSavings.SAVINGS_CONTROL.getValue(),
CashAccountsForSavings.SAVINGS_CONTROL.toString(), changes);
mergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
savingsProductId, CashAccountsForSavings.TRANSFERS_SUSPENSE.getValue(),
CashAccountsForSavings.TRANSFERS_SUSPENSE.toString(), changes);
createOrmergeSavingsToLiabilityAccountMappingChanges(element, SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(),
savingsProductId, CashAccountsForSavings.ESCHEAT_LIABILITY.getValue(), changes);
break;
case ACCRUAL_PERIODIC:
break;
case ACCRUAL_UPFRONT:
break;
}
}
public void deleteSavingsProductToGLAccountMapping(final Long savingsProductId) {
deleteProductToGLAccountMapping(savingsProductId, PortfolioProductType.SAVING);
}
}
@@ -0,0 +1,90 @@
/**
* 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.infrastructure.dataqueries.api;
/**
* Created by Cieyou on 2/26/14.
*/
public final class DataTableApiConstant {
private DataTableApiConstant() {
}
public static final Integer CATEGORY_PPI = 200;
public static final Integer CATEGORY_DEFAULT = 100;
public static final String categoryParamName = "category";
public static final String localParamName = "locale";
public static final String DATATABLE_RESOURCE_NAME = "dataTables";
public static final String CREATEDAT_FIELD_NAME = "created_at";
public static final String UPDATEDAT_FIELD_NAME = "updated_at";
// associationParameters
public static final String allAssociateParamName = "all";
public static final String repaymentScheduleAssociateParamName = "repaymentSchedule";
public static final String originalScheduleAssociateParamName = "originalSchedule";
public static final String transactionsAssociateParamName = "transactions";
public static final String chargesAssociateParamName = "charges";
public static final String guarantorsAssociateParamName = "guarantors";
public static final String collateralAssociateParamName = "collateral";
public static final String notesAssociateParamName = "notes";
public static final String linkedAccountAssociateParamName = "linkedAccount";
public static final String multiDisburseDetailsAssociateParamName = "multiDisburseDetails";
public static final String futureScheduleAssociateParamName = "futureSchedule";
public static final String meetingAssociateParamName = "meeting";
public static final String emiAmountVariationsAssociateParamName = "emiAmountVariations";
public static final String collectionAssociateParamName = "collection";
public static final String TABLE_COLUMN_CODE_MAPPINGS = "x_table_column_code_mappings";
public static final String TABLE_REGISTERED_TABLE = "x_registered_table";
public static final String TABLE_FIELD_ID = "id";
public static final String API_PARAM_COLUMNS = "columns";
public static final String API_PARAM_CHANGECOLUMNS = "changeColumns";
public static final String API_PARAM_ADDCOLUMNS = "addColumns";
public static final String API_PARAM_DROPCOLUMNS = "dropColumns";
public static final String API_PARAM_DATATABLE_NAME = "datatableName";
public static final String API_PARAM_APPTABLE_NAME = "apptableName";
public static final String API_PARAM_SUBTYPE = "entitySubType";
public static final String API_PARAM_MULTIROW = "multiRow";
public static final String API_FIELD_NAME = "name";
public static final String API_FIELD_NEWNAME = "newName";
public static final String API_FIELD_TYPE = "type";
public static final String API_FIELD_LENGTH = "length";
public static final String API_FIELD_MANDATORY = "mandatory";
public static final String API_FIELD_UNIQUE = "unique";
public static final String API_FIELD_INDEXED = "indexed";
public static final String API_FIELD_AFTER = "after";
public static final String API_FIELD_CODE = "code";
public static final String API_FIELD_NEWCODE = "newCode";
public static final String API_FIELD_TYPE_STRING = "string";
public static final String API_FIELD_TYPE_NUMBER = "number";
public static final String API_FIELD_TYPE_BOOLEAN = "boolean";
public static final String API_FIELD_TYPE_DECIMAL = "decimal";
public static final String API_FIELD_TYPE_DATE = "date";
public static final String API_FIELD_TYPE_DATETIME = "datetime";
public static final String API_FIELD_TYPE_TIMESTAMP = "timestamp";
public static final String API_FIELD_TYPE_TEXT = "text";
public static final String API_FIELD_TYPE_JSON = "json";
public static final String API_FIELD_TYPE_DROPDOWN = "dropdown";
}
@@ -0,0 +1,131 @@
/**
* 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.infrastructure.dataqueries.data;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_COLUMN;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_FILTERS;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_OPERATOR;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_QUERY;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_RESULTCOLUMNS;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_TABLE;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import jakarta.validation.constraints.NotNull;
import java.lang.reflect.Type;
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.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.apache.fineract.portfolio.search.data.AdvancedQueryRequest;
import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.data.FilterData;
import org.apache.fineract.portfolio.search.data.TableQueryData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class DataTableValidator {
private final FromJsonHelper fromApiJsonHelper;
private static final Set<String> SUPPORTED_PARAMETERS = new HashSet<>(
Arrays.asList(DataTableApiConstant.categoryParamName, DataTableApiConstant.localParamName));
@Autowired
public DataTableValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateDataTableRegistration(final String json) {
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(DataTableApiConstant.DATATABLE_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
if (this.fromApiJsonHelper.parameterExists(DataTableApiConstant.categoryParamName, element)) {
final Integer category = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(DataTableApiConstant.categoryParamName, element);
Object[] objectArray = new Integer[] { DataTableApiConstant.CATEGORY_PPI, DataTableApiConstant.CATEGORY_DEFAULT };
baseDataValidator.reset().parameter(DataTableApiConstant.categoryParamName).value(category).isOneOfTheseValues(objectArray);
}
baseDataValidator.throwValidationErrors();
}
public void validateTableSearch(@NotNull AdvancedQueryRequest queryRequest) {
final List<ApiParameterError> errors = new ArrayList<>();
final DataValidatorBuilder validator = new DataValidatorBuilder(errors).resource(DataTableApiConstant.DATATABLE_RESOURCE_NAME);
AdvancedQueryData baseQuery = queryRequest.getBaseQuery();
if (baseQuery != null) {
validateQueryData(baseQuery, validator);
}
List<TableQueryData> datatableQueries = queryRequest.getDatatableQueries();
if (datatableQueries != null) {
for (TableQueryData datatableQuery : datatableQueries) {
validator.reset().parameter(API_PARAM_TABLE).value(datatableQuery.getTable()).notBlank();
AdvancedQueryData queryData = datatableQuery.getQuery();
validator.reset().parameter(API_PARAM_QUERY).value(queryData).notBlank();
if (queryData != null) {
validateQueryData(queryData, validator);
}
}
}
validator.throwValidationErrors();
}
public void validateTableSearch(@NotNull AdvancedQueryData queryData) {
final DataValidatorBuilder validator = new DataValidatorBuilder(new ArrayList<>())
.resource(DataTableApiConstant.DATATABLE_RESOURCE_NAME);
validateQueryData(queryData, validator);
validator.throwValidationErrors();
}
private void validateQueryData(@NotNull AdvancedQueryData queryData, @NotNull DataValidatorBuilder validator) {
List<ColumnFilterData> columnFilters = queryData.getColumnFilters();
if (columnFilters != null) {
for (ColumnFilterData columnFilter : columnFilters) {
validator.reset().parameter(API_PARAM_COLUMN).value(columnFilter.getColumn()).notNull();
List<FilterData> filters = columnFilter.getFilters();
validator.reset().parameter(API_PARAM_FILTERS).value(filters == null ? null : filters.toArray()).notNull().arrayNotEmpty();
if (filters != null) {
for (FilterData filter : filters) {
validator.reset().parameter(API_PARAM_OPERATOR).value(filter.getOperator()).notNull();
}
}
}
List<String> resultColumns = queryData.getResultColumns();
if (resultColumns != null) {
for (String resultColumn : resultColumns) {
validator.reset().parameter(API_PARAM_RESULTCOLUMNS).value(resultColumn).notBlank();
}
}
}
}
}
@@ -0,0 +1,33 @@
/**
* 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.infrastructure.dataqueries.data;
import java.io.Serializable;
public class DatatableCheckStatusData implements Serializable {
private final String name;
private final int code;
public DatatableCheckStatusData(final String name, final int code) {
this.name = name;
this.code = code;
}
}
@@ -0,0 +1,33 @@
/**
* 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.infrastructure.dataqueries.data;
import java.io.Serializable;
public class DatatableChecksData implements Serializable {
private static final long serialVersionUID = 3113568562509206452L;
private final String entity;
private final String dataTableName;
public DatatableChecksData(final String entity, final String dataTableName) {
this.entity = entity;
this.dataTableName = dataTableName;
}
}
@@ -0,0 +1,50 @@
/**
* 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.infrastructure.dataqueries.data;
import java.io.Serializable;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
/**
* Immutable data object for role data.
*/
public class EntityDataTableChecksData implements Serializable {
private final long id;
private final String entity;
private final EnumOptionData status;
private final String datatableName;
private final boolean systemDefined;
private final Long order;
private final Long productId;
private final String productName;
public EntityDataTableChecksData(final long id, final String entity, final EnumOptionData status, final String datatableName,
final boolean systemDefined, final Long loanProductId, final String productName) {
this.id = id;
this.entity = entity;
this.status = status;
this.datatableName = datatableName;
this.systemDefined = systemDefined;
this.order = id;
this.productId = loanProductId;
this.productName = productName;
}
}
@@ -0,0 +1,140 @@
/**
* 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.infrastructure.dataqueries.data;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.ACTIVATE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.APPROVE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.CLOSE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.CREATE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.DISBURSE;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.REJECTED;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.WITHDRAWN;
import static org.apache.fineract.infrastructure.dataqueries.data.StatusEnum.WRITE_OFF;
import com.google.common.collect.ImmutableList;
import jakarta.validation.constraints.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
public enum EntityTables {
CLIENT("m_client", "client_id", "id", CREATE, ACTIVATE, CLOSE), //
GROUP("m_group", "group_id", "id", CREATE, ACTIVATE, CLOSE), //
CENTER("m_center", "m_group", "center_id", "id"), //
OFFICE("m_office", "office_id", "id"), //
LOAN_PRODUCT("m_product_loan", "product_loan_id", "id"), //
LOAN("m_loan", "loan_id", "id", CREATE, APPROVE, DISBURSE, WITHDRAWN, REJECTED, WRITE_OFF), //
SAVINGS_PRODUCT("m_savings_product", "savings_product_id", "id"), //
SAVINGS("m_savings_account", "savings_account_id", "id", CREATE, APPROVE, ACTIVATE, WITHDRAWN, REJECTED, CLOSE), //
SAVINGS_TRANSACTION("m_savings_account_transaction", "savings_transaction_id", "id"), //
SHARE_PRODUCT("m_share_product", "share_product_id", "id"), //
;
public static final EntityTables[] VALUES = values();
private static final List<String> ENTITY_NAMES = Arrays.stream(VALUES).map(EntityTables::getName).toList();
private static final Map<String, EntityTables> BY_ENTITY_NAME = Arrays.stream(VALUES)
.collect(Collectors.toMap(EntityTables::getName, e -> e));
@NotNull
private final String name;
@NotNull
private final String apptableName;
@NotNull
private final String foreignKeyColumnNameOnDatatable;
@NotNull
private final String refColumn; // referenced column name on apptable
private final ImmutableList<StatusEnum> checkStatuses;
EntityTables(@NotNull String name, @NotNull String apptableName, @NotNull String foreignKeyColumnNameOnDatatable,
@NotNull String refColumn, StatusEnum... statuses) {
this.name = name;
this.apptableName = apptableName;
this.foreignKeyColumnNameOnDatatable = foreignKeyColumnNameOnDatatable;
this.refColumn = refColumn;
this.checkStatuses = statuses == null ? ImmutableList.of() : ImmutableList.copyOf(statuses);
}
EntityTables(@NotNull String name, @NotNull String foreignKeyColumnNameOnDatatable, @NotNull String refColumn, StatusEnum... statuses) {
this(name, name, foreignKeyColumnNameOnDatatable, refColumn, statuses);
}
@NotNull
public String getName() {
return name;
}
@NotNull
public String getApptableName() {
return apptableName;
}
@NotNull
public String getForeignKeyColumnNameOnDatatable() {
return this.foreignKeyColumnNameOnDatatable;
}
@NotNull
public String getRefColumn() {
return refColumn;
}
public List<StatusEnum> getCheckStatuses() {
return checkStatuses;
}
public boolean hasCheck() {
return checkStatuses != null && !checkStatuses.isEmpty();
}
public static List<String> getEntityNames() {
return ENTITY_NAMES;
}
public static EntityTables fromEntityName(String name) {
return name == null ? null : BY_ENTITY_NAME.get(name.toLowerCase());
}
public static String getForeignKeyColumnNameOnDatatable(String name) {
EntityTables entityTable = fromEntityName(name);
return entityTable == null ? null : entityTable.getForeignKeyColumnNameOnDatatable();
}
@NotNull
public static List<StatusEnum> getCheckStatuses(String name) {
EntityTables entityTable = fromEntityName(name);
return entityTable == null ? List.of() : entityTable.getCheckStatuses();
}
@NotNull
public static List<Integer> getCheckStatusCodes(String name) {
return getCheckStatuses(name).stream().map(StatusEnum::getCode).toList();
}
@NotNull
public static List<EntityTables> getFiltered(Predicate<EntityTables> filter) {
return Arrays.stream(VALUES).filter(filter).toList();
}
}
@@ -0,0 +1,65 @@
/**
* 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.infrastructure.dataqueries.data;
import java.util.List;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
/**
* Immutable data object for generic resultset data.
*/
public final class GenericResultsetData {
private final List<ResultsetColumnHeaderData> columnHeaders;
private final List<ResultsetRowData> data;
public GenericResultsetData(final List<ResultsetColumnHeaderData> columnHeaders, final List<ResultsetRowData> resultsetDataRows) {
this.columnHeaders = columnHeaders;
this.data = resultsetDataRows;
}
public List<ResultsetColumnHeaderData> getColumnHeaders() {
return this.columnHeaders;
}
public List<ResultsetRowData> getData() {
return this.data;
}
public JdbcJavaType getColTypeOfColumnNamed(final String columnName) {
for (final ResultsetColumnHeaderData columnHeader : this.columnHeaders) {
if (columnHeader.isNamed(columnName)) {
return columnHeader.getColumnType();
}
}
return null;
}
public boolean hasNoEntries() {
return this.data.isEmpty();
}
public boolean hasEntries() {
return !hasNoEntries();
}
public boolean hasMoreThanOneEntry() {
return this.data.size() > 1;
}
}
@@ -0,0 +1,104 @@
/**
* 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.infrastructure.dataqueries.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ReportData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final String reportName;
@SuppressWarnings("unused")
private final String reportType;
@SuppressWarnings("unused")
private final String reportSubType;
@SuppressWarnings("unused")
private final String reportCategory;
@SuppressWarnings("unused")
private final String description;
@SuppressWarnings("unused")
private final String reportSql;
@SuppressWarnings("unused")
private final Boolean coreReport;
@SuppressWarnings("unused")
private final Boolean useReport;
@SuppressWarnings("unused")
private final Collection<ReportParameterData> reportParameters;
@SuppressWarnings("unused")
private List<String> allowedReportTypes;
@SuppressWarnings("unused")
private List<String> allowedReportSubTypes;
@SuppressWarnings("unused")
private Collection<ReportParameterData> allowedParameters;
public ReportData(final Long id, final String reportName, final String reportType, final String reportSubType,
final String reportCategory, final String description, final String reportSql, final Boolean coreReport,
final Boolean useReport, final Collection<ReportParameterData> reportParameters) {
this.id = id;
this.reportName = reportName;
this.reportType = reportType;
this.reportSubType = reportSubType;
this.reportCategory = reportCategory;
this.description = description;
this.reportParameters = reportParameters;
this.reportSql = reportSql;
this.coreReport = coreReport;
this.useReport = useReport;
this.allowedReportTypes = null;
this.allowedReportSubTypes = null;
this.allowedParameters = null;
}
public ReportData() {
this.id = null;
this.reportName = null;
this.reportType = null;
this.reportSubType = null;
this.reportCategory = null;
this.description = null;
this.reportParameters = null;
this.reportSql = null;
this.coreReport = null;
this.useReport = null;
this.allowedReportTypes = null;
this.allowedReportSubTypes = null;
this.allowedParameters = null;
}
public void appendedTemplate(final Collection<ReportParameterData> allowedParameters, final Collection<String> allowedReportTypes) {
final List<String> reportTypes = new ArrayList<>();
reportTypes.addAll(allowedReportTypes);
this.allowedReportTypes = reportTypes;
final List<String> reportSubTypes = new ArrayList<>();
reportSubTypes.add("Bar");
reportSubTypes.add("Pie");
this.allowedReportSubTypes = reportSubTypes;
this.allowedParameters = allowedParameters;
}
}
@@ -0,0 +1,31 @@
/**
* 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.infrastructure.dataqueries.data;
import java.io.Serializable;
import lombok.Data;
import lombok.RequiredArgsConstructor;
@Data
@RequiredArgsConstructor
public class ReportExportType implements Serializable {
private final String key;
private final String queryParameter;
}
@@ -0,0 +1,39 @@
/**
* 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.infrastructure.dataqueries.data;
/* used to show list of parameters used by a report and also for getting a list of parameters available (the reportParameterName is left null */
public final class ReportParameterData {
@SuppressWarnings("unused")
private final Long id;
@SuppressWarnings("unused")
private final Long parameterId;
@SuppressWarnings("unused")
private final String parameterName;
@SuppressWarnings("unused")
private final String reportParameterName;
public ReportParameterData(final Long id, final Long parameterId, final String reportParameterName, final String parameterName) {
this.id = id;
this.parameterId = parameterId;
this.parameterName = parameterName;
this.reportParameterName = reportParameterName;
}
}
@@ -0,0 +1,108 @@
/**
* 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.infrastructure.dataqueries.data;
public final class ReportParameterJoinData {
private final Long reportId;
private final String reportName;
private final String reportType;
private final String reportSubType;
private final String reportCategory;
private final String description;
private final String reportSql;
private final Boolean coreReport;
private final Boolean useReport;
private final Long reportParameterId;
private final Long parameterId;
private final String reportParameterName;
private final String parameterName;
public ReportParameterJoinData(final Long reportId, final String reportName, final String reportType, final String reportSubType,
final String reportCategory, final String description, final String reportSql, final Boolean coreReport,
final Boolean useReport, final Long reportParameterId, final Long parameterId, final String reportParameterName,
final String parameterName) {
this.reportId = reportId;
this.reportName = reportName;
this.reportType = reportType;
this.reportSubType = reportSubType;
this.reportCategory = reportCategory;
this.description = description;
this.reportSql = reportSql;
this.coreReport = coreReport;
this.useReport = useReport;
this.reportParameterId = reportParameterId;
this.parameterId = parameterId;
this.reportParameterName = reportParameterName;
this.parameterName = parameterName;
}
public Long getReportId() {
return this.reportId;
}
public String getReportName() {
return this.reportName;
}
public String getReportType() {
return this.reportType;
}
public String getReportSubType() {
return this.reportSubType;
}
public String getReportCategory() {
return this.reportCategory;
}
public String getReportSql() {
return this.reportSql;
}
public Boolean getCoreReport() {
return this.coreReport;
}
public Boolean getUseReport() {
return this.useReport;
}
public Long getReportParameterId() {
return this.reportParameterId;
}
public Long getParameterId() {
return this.parameterId;
}
public String getReportParameterName() {
return this.reportParameterName;
}
public String getParameterName() {
return this.parameterName;
}
public String getDescription() {
return this.description;
}
}
@@ -0,0 +1,38 @@
/**
* 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.infrastructure.dataqueries.data;
import java.util.List;
public final class ResultsetRowData {
private final List<Object> row;
public static ResultsetRowData create(final List<Object> rowValues) {
return new ResultsetRowData(rowValues);
}
private ResultsetRowData(final List<Object> rowValues) {
this.row = rowValues;
}
public List<Object> getRow() {
return this.row;
}
}
@@ -0,0 +1,105 @@
/**
* 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.infrastructure.dataqueries.data;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public enum StatusEnum {
CREATE("create", 100), APPROVE("approve", 200), ACTIVATE("activate", 300), WITHDRAWN("withdraw", 400), REJECTED("reject", 500), CLOSE(
"close", 600), WRITE_OFF("write off", 601), RESCHEDULE("reschedule", 602), OVERPAY("overpay", 700), DISBURSE("disburse", 800);
private final String name;
private final Integer code;
public Integer getCode() {
return code;
}
StatusEnum(String name, Integer code) {
this.name = name;
this.code = code;
}
public static List<DatatableCheckStatusData> getStatusList() {
List<DatatableCheckStatusData> data = new ArrayList<DatatableCheckStatusData>();
for (StatusEnum status : StatusEnum.values()) {
data.add(new DatatableCheckStatusData(status.name, status.code));
}
return data;
}
public static StatusEnum fromInt(final Integer code) {
StatusEnum ret = null;
switch (code) {
case 100:
ret = StatusEnum.CREATE;
break;
case 200:
ret = StatusEnum.APPROVE;
break;
case 300:
ret = StatusEnum.ACTIVATE;
break;
case 400:
ret = StatusEnum.WITHDRAWN;
break;
case 500:
ret = StatusEnum.REJECTED;
break;
case 600:
ret = StatusEnum.CLOSE;
break;
case 601:
ret = StatusEnum.WRITE_OFF;
break;
case 602:
ret = StatusEnum.RESCHEDULE;
break;
case 700:
ret = StatusEnum.OVERPAY;
break;
case 800:
ret = StatusEnum.DISBURSE;
break;
default:
break;
}
return ret;
}
public static EnumOptionData statusTypeEnum(final Integer id) {
return statusType(StatusEnum.fromInt(id));
}
public static EnumOptionData statusType(final StatusEnum statusType) {
final EnumOptionData optionData = new EnumOptionData(statusType.getCode().longValue(), statusType.name(), statusType.name());
return optionData;
}
}
@@ -0,0 +1,43 @@
/**
* 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.infrastructure.dataqueries.service;
import java.util.List;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetRowData;
public interface GenericDataService {
GenericResultsetData fillGenericResultSet(String sql);
List<ResultsetColumnHeaderData> fillResultsetColumnHeaders(String tableName);
List<ResultsetRowData> fillResultsetRowData(String sql, List<ResultsetColumnHeaderData> columnHeaders);
String generateJsonFromGenericResultsetData(GenericResultsetData grs);
String replace(String str, String pattern, String replace);
String wrapSQL(String sql);
boolean isExplicitlyUnique(String tableName, String columnName);
boolean isExplicitlyIndexed(String tableName, String columnName);
}
@@ -0,0 +1,89 @@
/**
* 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.infrastructure.dataqueries.service;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.util.List;
import java.util.Locale;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.service.PagedLocalRequest;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
import org.apache.fineract.infrastructure.dataqueries.data.GenericResultsetData;
import org.apache.fineract.portfolio.search.data.AdvancedQueryData;
import org.springframework.data.domain.Page;
import org.springframework.security.access.prepost.PreAuthorize;
public interface ReadWriteNonCoreDataService {
List<DatatableData> retrieveDatatableNames(String appTable);
DatatableData retrieveDatatable(String datatable);
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
void registerDatatable(JsonCommand command);
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
void registerDatatable(String dataTableName, String applicationTableName, String entitySubType);
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'REGISTER_DATATABLE')")
void registerDatatable(JsonCommand command, String permissionTable);
@PreAuthorize(value = "hasAnyAuthority('ALL_FUNCTIONS', 'DEREGISTER_DATATABLE')")
void deregisterDatatable(String datatable);
GenericResultsetData retrieveDataTableGenericResultSet(String datatable, Long appTableId, String order, Long id);
CommandProcessingResult createDatatable(JsonCommand command);
void updateDatatable(String datatableName, JsonCommand command);
void deleteDatatable(String datatableName);
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult createNewDatatableEntry(String datatable, Long appTableId, String json);
CommandProcessingResult createPPIEntry(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult updateDatatableEntryOneToOne(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult updateDatatableEntryOneToMany(String datatable, Long appTableId, Long datatableId, JsonCommand command);
CommandProcessingResult deleteDatatableEntries(String datatable, Long appTableId, JsonCommand command);
CommandProcessingResult deleteDatatableEntry(String datatable, Long appTableId, Long datatableId, JsonCommand command);
String getTableName(String Url);
String getDataTableName(String Url);
Long countDatatableEntries(String datatableName, Long appTableId, String foreignKeyColumn);
List<JsonObject> queryDataTable(@NotNull String datatable, @NotNull String columnName, String columnValue,
@NotNull String resultColumns);
Page<JsonObject> queryDataTableAdvanced(@NotNull String datatable, @NotNull PagedLocalRequest<AdvancedQueryData> pagedRequest);
boolean buildDataQueryEmbedded(@NotNull EntityTables entityTable, @NotNull String datatable, @NotNull AdvancedQueryData request,
@NotNull List<String> selectColumns, @NotNull StringBuilder select, @NotNull StringBuilder from, @NotNull StringBuilder where,
@NotNull List<Object> params, String mainAlias, String alias, String dateFormat, String dateTimeFormat, Locale locale);
}
@@ -0,0 +1,32 @@
/**
* 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.organisation.monetary.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
/**
* {@link AbstractPlatformDomainRuleException} thrown when currency mismatch occurs
*/
public class InvalidCurrencyException extends AbstractPlatformDomainRuleException {
public InvalidCurrencyException(final String entity, final String postFix, final String defaultUserMessage,
final Object... defaultUserMessageArgs) {
super("error.msg." + entity + "." + postFix + ".invalid.currency", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -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.charge.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class ChargeCannotBeAppliedToException extends AbstractPlatformDomainRuleException {
public ChargeCannotBeAppliedToException(final String postFix, final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.charge.cannot.be.applied.to" + postFix, defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -0,0 +1,52 @@
/**
* 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.collectionsheet;
public final class CollectionSheetConstants {
private CollectionSheetConstants() {
}
public static final String COLLECTIONSHEET_RESOURCE_NAME = "collectionsheet";
public static final String localeParamName = "locale";
public static final String dateFormatParamName = "dateFormat";
public static final String transactionDateParamName = "transactionDate";
public static final String actualDisbursementDateParamName = "actualDisbursementDate";
public static final String bulkRepaymentTransactionsParamName = "bulkRepaymentTransactions";
public static final String bulkDisbursementTransactionsParamName = "bulkDisbursementTransactions";
public static final String bulkSavingsDueTransactionsParamName = "bulkSavingsDueTransactions";
public static final String noteParamName = "note";
public static final String calendarIdParamName = "calendarId";
public static final String officeIdParamName = "officeId";
public static final String staffIdParamName = "staffId";
public static final String isTransactionDateOnNonMeetingDateParamName = "isTransactionDateOnNonMeetingDate";
// attendance parameters
public static final String clientsAttendanceParamName = "clientsAttendance";
public static final String clientIdParamName = "clientId";
public static final String attendanceTypeParamName = "attendanceType";
public static final String loanIdParamName = "loanId";
public static final String savingsIdParamName = "savingsId";
public static final String transactionAmountParamName = "transactionAmount";
public static final String depositAccountTypeParamName = "depositAccountType";
}
@@ -0,0 +1,81 @@
/**
* 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.common.domain;
import java.util.Arrays;
public enum ConditionType {
INVALID(0, "ConditionType.invalid"), //
LESSTHAN(1, "ConditionType.lessthan"), //
EQUAL(2, "ConditionType.equal"), //
// TODO: fix typo "GREATERTHAN"
GRETERTHAN(3, "ConditionType.greterthan"), //
NOT_EQUAL(4, "ConditionType.notequal");//
private final Integer value;
private final String code;
public static ConditionType fromInt(final Integer v) {
if (v == null) {
return INVALID;
}
switch (v) {
case 1:
return LESSTHAN;
case 2:
return EQUAL;
case 3:
return GRETERTHAN;
case 4:
return NOT_EQUAL;
default:
return INVALID;
}
}
ConditionType(final Integer value, final String code) {
this.value = value;
this.code = code;
}
@Override
public String toString() {
return name().toString().replace("_", " ");
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
// TODO: why not just use the enum values... just more boilerplate code here!!
public boolean isInvalid() {
return ConditionType.INVALID.getValue().equals(this.value);
}
// TODO: do we really need this?!?
public static Object[] integerValues() {
return Arrays.stream(values()).filter(value -> !INVALID.equals(value)).map(value -> value.value).toList().toArray();
}
}
@@ -0,0 +1,170 @@
/**
* 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.common.service;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.common.domain.ConditionType;
import org.apache.fineract.portfolio.common.domain.DaysInMonthType;
import org.apache.fineract.portfolio.common.domain.DaysInYearType;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
public final class CommonEnumerations {
private CommonEnumerations() {
}
public static final String DAYS_IN_MONTH_TYPE = "daysInMonthType";
public static final String DAYS_IN_YEAR_TYPE = "daysInYearType";
public static EnumOptionData commonEnumueration(final String typeName, final int id) {
EnumOptionData enumData = null;
if (typeName.equals(DAYS_IN_MONTH_TYPE)) {
enumData = daysInMonthType(id);
} else if (typeName.equals(DAYS_IN_YEAR_TYPE)) {
enumData = daysInYearType(id);
}
return enumData;
}
public static EnumOptionData termFrequencyType(final int id, final String codePrefix) {
return termFrequencyType(PeriodFrequencyType.fromInt(id), codePrefix);
}
public static EnumOptionData termFrequencyType(final PeriodFrequencyType type, final String codePrefix) {
EnumOptionData optionData = null;
switch (type) {
case DAYS:
optionData = new EnumOptionData(PeriodFrequencyType.DAYS.getValue().longValue(),
codePrefix + PeriodFrequencyType.DAYS.getCode(), "Days");
break;
case WEEKS:
optionData = new EnumOptionData(PeriodFrequencyType.WEEKS.getValue().longValue(),
codePrefix + PeriodFrequencyType.WEEKS.getCode(), "Weeks");
break;
case MONTHS:
optionData = new EnumOptionData(PeriodFrequencyType.MONTHS.getValue().longValue(),
codePrefix + PeriodFrequencyType.MONTHS.getCode(), "Months");
break;
case YEARS:
optionData = new EnumOptionData(PeriodFrequencyType.YEARS.getValue().longValue(),
codePrefix + PeriodFrequencyType.YEARS.getCode(), "Years");
break;
default:
optionData = new EnumOptionData(PeriodFrequencyType.INVALID.getValue().longValue(), PeriodFrequencyType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
public static EnumOptionData conditionType(final int id, final String codePrefix) {
return conditionType(ConditionType.fromInt(id), codePrefix);
}
public static EnumOptionData conditionType(final ConditionType type, final String codePrefix) {
EnumOptionData optionData = null;
switch (type) {
case EQUAL:
optionData = new EnumOptionData(ConditionType.EQUAL.getValue().longValue(), codePrefix + ConditionType.EQUAL.getCode(),
"equal");
break;
case NOT_EQUAL:
optionData = new EnumOptionData(ConditionType.NOT_EQUAL.getValue().longValue(),
codePrefix + ConditionType.NOT_EQUAL.getCode(), "notEqual");
break;
case GRETERTHAN:
optionData = new EnumOptionData(ConditionType.GRETERTHAN.getValue().longValue(),
codePrefix + ConditionType.GRETERTHAN.getCode(), "greterthan");
break;
case LESSTHAN:
optionData = new EnumOptionData(ConditionType.LESSTHAN.getValue().longValue(),
codePrefix + ConditionType.LESSTHAN.getCode(), "lessthan");
break;
default:
optionData = new EnumOptionData(ConditionType.INVALID.getValue().longValue(), ConditionType.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
public static List<EnumOptionData> conditionType(final ConditionType[] conditionTypes, final String codePrefix) {
final List<EnumOptionData> optionDatas = new ArrayList<>();
for (final ConditionType conditionType : conditionTypes) {
if (!conditionType.isInvalid()) {
optionDatas.add(conditionType(conditionType, codePrefix));
}
}
return optionDatas;
}
public static EnumOptionData daysInMonthType(final int id) {
return daysInMonthType(DaysInMonthType.fromInt(id));
}
public static EnumOptionData daysInMonthType(final DaysInMonthType type) {
EnumOptionData optionData = null;
switch (type) {
case ACTUAL:
optionData = new EnumOptionData(DaysInMonthType.ACTUAL.getValue().longValue(), DaysInMonthType.ACTUAL.getCode(), "Actual");
break;
case DAYS_30:
optionData = new EnumOptionData(DaysInMonthType.DAYS_30.getValue().longValue(), DaysInMonthType.DAYS_30.getCode(),
"30 Days");
break;
default:
optionData = new EnumOptionData(DaysInMonthType.INVALID.getValue().longValue(), DaysInMonthType.INVALID.getCode(),
"Invalid");
break;
}
return optionData;
}
public static EnumOptionData daysInYearType(final int id) {
return daysInYearType(DaysInYearType.fromInt(id));
}
public static EnumOptionData daysInYearType(final DaysInYearType type) {
EnumOptionData optionData = null;
switch (type) {
case ACTUAL:
optionData = new EnumOptionData(DaysInYearType.ACTUAL.getValue().longValue(), DaysInYearType.ACTUAL.getCode(), "Actual");
break;
case DAYS_360:
optionData = new EnumOptionData(DaysInYearType.DAYS_360.getValue().longValue(), DaysInYearType.DAYS_360.getCode(),
"360 Days");
break;
case DAYS_364:
optionData = new EnumOptionData(DaysInYearType.DAYS_364.getValue().longValue(), DaysInYearType.DAYS_364.getCode(),
"364 Days");
break;
case DAYS_365:
optionData = new EnumOptionData(DaysInYearType.DAYS_365.getValue().longValue(), DaysInYearType.DAYS_365.getCode(),
"365 Days");
break;
default:
optionData = new EnumOptionData(DaysInYearType.INVALID.getValue().longValue(), DaysInYearType.INVALID.getCode(), "Invalid");
break;
}
return optionData;
}
}
@@ -0,0 +1,155 @@
/**
* 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.domain;
/**
* Enum representation of loan status states.
*/
public enum LoanStatus {
INVALID(0, "loanStatusType.invalid"), //
SUBMITTED_AND_PENDING_APPROVAL(100, "loanStatusType.submitted.and.pending.approval"), //
APPROVED(200, "loanStatusType.approved"), //
ACTIVE(300, "loanStatusType.active"), //
TRANSFER_IN_PROGRESS(303, "loanStatusType.transfer.in.progress"), //
TRANSFER_ON_HOLD(304, "loanStatusType.transfer.on.hold"), //
WITHDRAWN_BY_CLIENT(400, "loanStatusType.withdrawn.by.client"), //
REJECTED(500, "loanStatusType.rejected"), //
CLOSED_OBLIGATIONS_MET(600, "loanStatusType.closed.obligations.met"), //
CLOSED_WRITTEN_OFF(601, "loanStatusType.closed.written.off"), //
CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT(602, "loanStatusType.closed.reschedule.outstanding.amount"), //
OVERPAID(700, "loanStatusType.overpaid");
private final Integer value;
private final String code;
public static LoanStatus fromInt(final Integer statusValue) {
LoanStatus enumeration = LoanStatus.INVALID;
switch (statusValue) {
case 100:
enumeration = LoanStatus.SUBMITTED_AND_PENDING_APPROVAL;
break;
case 200:
enumeration = LoanStatus.APPROVED;
break;
case 300:
enumeration = LoanStatus.ACTIVE;
break;
case 303:
enumeration = LoanStatus.TRANSFER_IN_PROGRESS;
break;
case 304:
enumeration = LoanStatus.TRANSFER_ON_HOLD;
break;
case 400:
enumeration = LoanStatus.WITHDRAWN_BY_CLIENT;
break;
case 500:
enumeration = LoanStatus.REJECTED;
break;
case 600:
enumeration = LoanStatus.CLOSED_OBLIGATIONS_MET;
break;
case 601:
enumeration = LoanStatus.CLOSED_WRITTEN_OFF;
break;
case 602:
enumeration = LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT;
break;
case 700:
enumeration = LoanStatus.OVERPAID;
break;
}
return enumeration;
}
LoanStatus(final Integer value, final String code) {
this.value = value;
this.code = code;
}
public boolean hasStateOf(final LoanStatus state) {
return this.value.equals(state.getValue());
}
public Integer getValue() {
return this.value;
}
public String getCode() {
return this.code;
}
public boolean isSubmittedAndPendingApproval() {
return this.value.equals(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue());
}
public boolean isApproved() {
return this.value.equals(LoanStatus.APPROVED.getValue());
}
public boolean isActive() {
return this.value.equals(LoanStatus.ACTIVE.getValue());
}
public boolean isClosed() {
return isClosedObligationsMet() || isClosedWrittenOff() || isClosedWithOutsandingAmountMarkedForReschedule();
}
public boolean isClosedObligationsMet() {
return this.value.equals(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue());
}
public boolean isClosedWrittenOff() {
return this.value.equals(LoanStatus.CLOSED_WRITTEN_OFF.getValue());
}
public boolean isClosedWithOutsandingAmountMarkedForReschedule() {
return this.value.equals(LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT.getValue());
}
public boolean isWithdrawnByClient() {
return this.value.equals(LoanStatus.WITHDRAWN_BY_CLIENT.getValue());
}
public boolean isRejected() {
return this.value.equals(LoanStatus.REJECTED.getValue());
}
public boolean isActiveOrAwaitingApprovalOrDisbursal() {
return isApproved() || isSubmittedAndPendingApproval() || isActive();
}
public boolean isTransferInProgress() {
return this.value.equals(LoanStatus.TRANSFER_IN_PROGRESS.getValue());
}
public boolean isTransferOnHold() {
return this.value.equals(LoanStatus.TRANSFER_ON_HOLD.getValue());
}
public boolean isUnderTransfer() {
return isTransferInProgress() || isTransferOnHold();
}
public boolean isOverpaid() {
return this.value.equals(LoanStatus.OVERPAID.getValue());
}
}
@@ -0,0 +1,169 @@
/**
* 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.search;
import java.util.HashSet;
import java.util.Set;
public final class SearchConstants {
public static final String API_PARAM_RESULTCOLUMNS = "resultColumns";
public static final String API_PARAM_COLUMN_FILTERS = "columnFilters";
public static final String API_PARAM_COLUMN = "column";
public static final String API_PARAM_FILTERS = "filters";
public static final String API_PARAM_OPERATOR = "operator";
public static final String API_PARAM_VALUES = "values";
public static final String API_PARAM_TABLE = "table";
public static final String API_PARAM_QUERY = "query";
public static final String API_PARAM_DATE_FORMAT = "dateFormat";
public static final String API_PARAM_DATETIME_FORMAT = "dateTimeFormat";
public static final String API_PARAM_LOCALE = "locale";
private SearchConstants() {}
public enum SearchResponseParameters {
ENTITY_ID("entityId"), ENTITY_ACCOUNT_NO("entityAccountNo"), ENTITY_EXTERNAL_ID("entityExternalId"), ENTITY_NAME(
"entityName"), ENTITY_TYPE("entityType"), PARENT_ID(
"parentId"), PARENT_NAME("parentName"), ENTITY_MOBILE_NO("entityMobileNo"), ENTITY_STATUS("entityStatus");
private final String value;
SearchResponseParameters(String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final SearchResponseParameters param : SearchResponseParameters.values()) {
values.add(param.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
public enum SearchSupportedParameters {
QUERY("query"), RESOURCE("resource"), EXACTMATCH("exactMatch");
private final String value;
SearchSupportedParameters(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final SearchSupportedParameters param : SearchSupportedParameters.values()) {
values.add(param.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
public enum SearchSupportedResources {
CLIENTS("clients"), GROUPS("groups"), LOANS("loans"), SAVINGS("savings"), SHARES("shares"), CLIENTIDENTIFIERS("clientIdentifiers");
private final String value;
SearchSupportedResources(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final SearchSupportedResources param : SearchSupportedResources.values()) {
values.add(param.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
public enum SearchLoanDate {
APPROVAL_DATE("approvalDate"), CREATED_DATE("createdDate"), DISBURSAL_DATE("disbursalDate");
private final String value;
SearchLoanDate(final String value) {
this.value = value;
}
private static final Set<String> values = new HashSet<>();
static {
for (final SearchLoanDate param : SearchLoanDate.values()) {
values.add(param.value);
}
}
public static Set<String> getAllValues() {
return values;
}
@Override
public String toString() {
return name().toString().replaceAll("_", " ");
}
public String getValue() {
return this.value;
}
}
}
@@ -0,0 +1,322 @@
/**
* 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.search.data;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.LocalDate;
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.lang3.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.infrastructure.security.utils.SQLInjectionValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AdHocQueryDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private static final Set<String> AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
AdHocQuerySearchConstants.entitiesParamName, AdHocQuerySearchConstants.loanStatusParamName,
AdHocQuerySearchConstants.loanProductsParamName, AdHocQuerySearchConstants.officesParamName,
AdHocQuerySearchConstants.loanDateOptionParamName, AdHocQuerySearchConstants.loanFromDateParamName,
AdHocQuerySearchConstants.loanToDateParamName, AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName,
AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName,
AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName,
AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName,
AdHocQuerySearchConstants.outStandingAmountPercentageParamName, AdHocQuerySearchConstants.includeOutstandingAmountParamName,
AdHocQuerySearchConstants.outstandingAmountConditionParamName, AdHocQuerySearchConstants.minOutstandingAmountParamName,
AdHocQuerySearchConstants.maxOutstandingAmountParamName, AdHocQuerySearchConstants.outstandingAmountParamName,
AdHocQuerySearchConstants.localeParamName, AdHocQuerySearchConstants.dateFormatParamName));
private static final Set<String> AD_HOC_SEARCH_QUERY_CONDITIONS = new HashSet<>(Arrays.asList("between", "<=", ">=", "<", ">", "="));
private static final Object[] loanDateOptions = { AdHocQuerySearchConstants.approvalDateOption,
AdHocQuerySearchConstants.createDateOption, AdHocQuerySearchConstants.disbursalDateOption };
private static final Object[] loanStatusOptions = { AdHocQuerySearchConstants.allLoanStatusOption,
AdHocQuerySearchConstants.activeLoanStatusOption, AdHocQuerySearchConstants.overpaidLoanStatusOption,
AdHocQuerySearchConstants.arrearsLoanStatusOption, AdHocQuerySearchConstants.closedLoanStatusOption,
AdHocQuerySearchConstants.writeoffLoanStatusOption };
@Autowired
public AdHocQueryDataValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
public void validateAdHocQueryParameters(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, AD_HOC_SEARCH_QUERY_REQUEST_DATA_PARAMETERS);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(AdHocQuerySearchConstants.AD_HOC_SEARCH_QUERY_RESOURCE_NAME);
final String[] entities = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.entitiesParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.entitiesParamName).value(entities).arrayNotEmpty();
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanStatusParamName, element)) {
final String[] loanStatus = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanStatusParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanStatusParamName).value(loanStatus).arrayNotEmpty();
if (loanStatus != null && loanStatus.length > 0) {
for (String status : loanStatus) {
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanStatusParamName).value(status)
.isOneOfTheseValues(loanStatusOptions);
}
}
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanProductsParamName, element)) {
final String[] loanProducts = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanProductsParamName,
element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanProductsParamName).value(loanProducts).arrayNotEmpty();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.officesParamName, element)) {
final String[] offices = this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.officesParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.officesParamName).value(offices).arrayNotEmpty();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
final String loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName,
element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanDateOptionParamName).value(loanDateOption)
.isOneOfTheseValues(loanDateOptions);
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanFromDateParamName, element)) {
final LocalDate loanFromDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanFromDateParamName,
element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanFromDateParamName).value(loanFromDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanToDateParamName, element)) {
final LocalDate loanToDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanToDateParamName,
element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.loanToDateParamName).value(loanToDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element)) {
final boolean includeOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBooleanNamed(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName)
.value(includeOutStandingAmountPercentage).notNull();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
final String outStandingAmountPercentageCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName)
.value(outStandingAmountPercentageCondition).isNotOneOfTheseValues(AD_HOC_SEARCH_QUERY_CONDITIONS);
if (outStandingAmountPercentageCondition.equals("between")) {
final BigDecimal minOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName)
.value(minOutStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
final BigDecimal maxOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName)
.value(maxOutStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
baseDataValidator.reset().compareMinAndMaxOfTwoBigDecmimalNos(minOutStandingAmountPercentage,
maxOutStandingAmountPercentage);
} else {
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element)) {
final BigDecimal outStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outStandingAmountPercentageParamName)
.value(outStandingAmountPercentage).notNull().notLessThanMin(BigDecimal.ZERO);
}
}
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element)) {
final Boolean includeOutstandingAmountParamName = this.fromApiJsonHelper
.extractBooleanNamed(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.includeOutstandingAmountParamName)
.value(includeOutstandingAmountParamName).notNull();
}
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
final String outstandingAmountCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outstandingAmountConditionParamName)
.value(outstandingAmountCondition).isNotOneOfTheseValues(AD_HOC_SEARCH_QUERY_CONDITIONS);
if (outstandingAmountCondition.equals("between")) {
final BigDecimal minOutstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.minOutstandingAmountParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.minOutstandingAmountParamName).value(minOutstandingAmount)
.notNull().notLessThanMin(BigDecimal.ZERO);
final BigDecimal maxOutstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutstandingAmountParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.maxOutstandingAmountParamName).value(maxOutstandingAmount)
.notNull().notLessThanMin(BigDecimal.ZERO);
baseDataValidator.reset().compareMinAndMaxOfTwoBigDecmimalNos(minOutstandingAmount, maxOutstandingAmount);
} else {
final BigDecimal outstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.outstandingAmountParamName, element);
baseDataValidator.reset().parameter(AdHocQuerySearchConstants.outstandingAmountParamName).value(outstandingAmount).notNull()
.notLessThanMin(BigDecimal.ZERO);
}
}
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public AdHocQuerySearchConditions retrieveSearchConditions(String json) {
validateAdHocQueryParameters(json);
final JsonElement element = this.fromApiJsonHelper.parse(json);
List<String> loanStatus = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanStatusParamName, element)) {
loanStatus = Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanStatusParamName, element));
}
List<Long> loanProducts = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanProductsParamName, element)) {
loanProducts = extractLongValuesList(
Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.loanProductsParamName, element)));
}
List<Long> offices = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.officesParamName, element)) {
offices = extractLongValuesList(
Arrays.asList(this.fromApiJsonHelper.extractArrayNamed(AdHocQuerySearchConstants.officesParamName, element)));
}
String loanDateOption = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanDateOptionParamName, element)) {
loanDateOption = this.fromApiJsonHelper.extractStringNamed(AdHocQuerySearchConstants.loanDateOptionParamName, element);
SQLInjectionValidator.validateSQLInput(loanDateOption);
}
LocalDate loanFromDate = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanFromDateParamName, element)) {
loanFromDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanFromDateParamName, element);
}
LocalDate loanToDate = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.loanToDateParamName, element)) {
loanToDate = this.fromApiJsonHelper.extractLocalDateNamed(AdHocQuerySearchConstants.loanToDateParamName, element);
}
Boolean includeOutStandingAmountPercentage = false;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element)) {
includeOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBooleanNamed(AdHocQuerySearchConstants.includeOutStandingAmountPercentageParamName, element);
}
String outStandingAmountPercentageCondition = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element)) {
outStandingAmountPercentageCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outStandingAmountPercentageConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outStandingAmountPercentageCondition);
}
BigDecimal minOutStandingAmountPercentage = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element)) {
minOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.minOutStandingAmountPercentageParamName, element);
}
BigDecimal maxOutStandingAmountPercentage = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element)) {
maxOutStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutStandingAmountPercentageParamName, element);
}
BigDecimal outStandingAmountPercentage = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element)) {
outStandingAmountPercentage = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.outStandingAmountPercentageParamName, element);
}
Boolean includeOutstandingAmountParamName = false;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element)) {
includeOutstandingAmountParamName = this.fromApiJsonHelper
.extractBooleanNamed(AdHocQuerySearchConstants.includeOutstandingAmountParamName, element);
}
String outstandingAmountCondition = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element)) {
outstandingAmountCondition = this.fromApiJsonHelper
.extractStringNamed(AdHocQuerySearchConstants.outstandingAmountConditionParamName, element);
SQLInjectionValidator.validateSQLInput(outstandingAmountCondition);
}
BigDecimal minOutstandingAmount = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.minOutstandingAmountParamName, element)) {
minOutstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.minOutstandingAmountParamName, element);
}
BigDecimal maxOutstandingAmount = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.maxOutstandingAmountParamName, element)) {
maxOutstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.maxOutstandingAmountParamName, element);
}
BigDecimal outstandingAmount = null;
if (this.fromApiJsonHelper.parameterExists(AdHocQuerySearchConstants.outstandingAmountParamName, element)) {
outstandingAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(AdHocQuerySearchConstants.outstandingAmountParamName, element);
}
return AdHocQuerySearchConditions.instance(loanStatus, loanProducts, offices, loanDateOption, loanFromDate, loanToDate,
includeOutStandingAmountPercentage, outStandingAmountPercentageCondition, minOutStandingAmountPercentage,
maxOutStandingAmountPercentage, outStandingAmountPercentage, includeOutstandingAmountParamName, outstandingAmountCondition,
minOutstandingAmount, maxOutstandingAmount, outstandingAmount);
}
private List<Long> extractLongValuesList(List<String> listTobeConverted) {
List<Long> tempList = new ArrayList<>();
for (String temp : listTobeConverted) {
tempList.add(Long.valueOf(temp));
}
return tempList;
}
}
@@ -0,0 +1,186 @@
/**
* 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.search.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
public class AdHocQuerySearchConditions {
private final List<String> loanStatus;
private final List<Long> loanProducts;
private final List<Long> offices;
private final String loanDateOption;
private final LocalDate loanFromDate;
private final LocalDate loanToDate;
private final Boolean includeOutStandingAmountPercentage;
private final String outStandingAmountPercentageCondition;
private final BigDecimal minOutStandingAmountPercentage;
private final BigDecimal maxOutStandingAmountPercentage;
private final BigDecimal outStandingAmountPercentage;
private final Boolean includeOutstandingAmount;
private final String outstandingAmountCondition;
private final BigDecimal minOutstandingAmount;
private final BigDecimal maxOutstandingAmount;
private final BigDecimal outstandingAmount;
public static AdHocQuerySearchConditions instance(final List<String> loanStatus, final List<Long> loanProducts,
final List<Long> offices, final String loanDateOption, final LocalDate loanFromDate, final LocalDate loanToDate,
final Boolean includeOutStandingAmountPercentage, final String outStandingAmountPercentageCondition,
final BigDecimal minOutStandingAmountPercentage, final BigDecimal maxOutStandingAmountPercentage,
final BigDecimal outStandingAmountPercentage, final Boolean includeOutstandingAmountParamName,
final String outstandingAmountCondition, final BigDecimal minOutstandingAmount, final BigDecimal maxOutstandingAmount,
final BigDecimal outstandingAmount) {
return new AdHocQuerySearchConditions(loanStatus, loanProducts, offices, loanDateOption, loanFromDate, loanToDate,
includeOutStandingAmountPercentage, outStandingAmountPercentageCondition, minOutStandingAmountPercentage,
maxOutStandingAmountPercentage, outStandingAmountPercentage, includeOutstandingAmountParamName, outstandingAmountCondition,
minOutstandingAmount, maxOutstandingAmount, outstandingAmount);
}
public AdHocQuerySearchConditions(final List<String> loanStatus, final List<Long> loanProducts, final List<Long> offices,
final String loanDateOption, final LocalDate loanFromDate, final LocalDate loanToDate,
final Boolean includeOutStandingAmountPercentage, final String outStandingAmountPercentageCondition,
final BigDecimal minOutStandingAmountPercentage, final BigDecimal maxOutStandingAmountPercentage,
final BigDecimal outStandingAmountPercentage, final Boolean includeOutstandingAmount, final String outstandingAmountCondition,
final BigDecimal minOutstandingAmount, final BigDecimal maxOutstandingAmount, final BigDecimal outstandingAmount) {
this.loanStatus = loanStatus;
this.loanProducts = loanProducts;
this.offices = offices;
this.loanDateOption = loanDateOption;
this.loanFromDate = loanFromDate;
this.loanToDate = loanToDate;
this.includeOutStandingAmountPercentage = includeOutStandingAmountPercentage;
this.outStandingAmountPercentageCondition = outStandingAmountPercentageCondition;
this.minOutStandingAmountPercentage = minOutStandingAmountPercentage;
this.maxOutStandingAmountPercentage = maxOutStandingAmountPercentage;
this.outStandingAmountPercentage = outStandingAmountPercentage;
this.includeOutstandingAmount = includeOutstandingAmount;
this.outstandingAmountCondition = outstandingAmountCondition;
this.minOutstandingAmount = minOutstandingAmount;
this.maxOutstandingAmount = maxOutstandingAmount;
this.outstandingAmount = outstandingAmount;
}
public List<String> getLoanStatus() {
return getStatusVluesFromStatusCodes();
}
private List<String> getStatusVluesFromStatusCodes() {
List<String> loanStatusValues = new ArrayList<>();
if (this.loanStatus != null) {
for (String statusCode : this.loanStatus) {
if (statusCode.equalsIgnoreCase("active")) {
loanStatusValues.add(LoanStatus.ACTIVE.getValue().toString());
}
if (statusCode.equalsIgnoreCase("overpaid")) {
loanStatusValues.add(LoanStatus.OVERPAID.getValue().toString());
}
if (statusCode.equalsIgnoreCase("closed")) {
loanStatusValues.add(LoanStatus.CLOSED_OBLIGATIONS_MET.getValue().toString());
}
if (statusCode.equalsIgnoreCase("writeoff")) {
loanStatusValues.add(LoanStatus.CLOSED_WRITTEN_OFF.getValue().toString());
}
if (statusCode.equalsIgnoreCase("arrears")) {
// TODO - No status code is there for loans which are in
// active bad standing
}
if (statusCode.equalsIgnoreCase("all")) {
loanStatusValues.add("all");
}
}
}
return loanStatusValues;
}
public List<Long> getLoanProducts() {
return this.loanProducts;
}
public List<Long> getOffices() {
return this.offices;
}
public String getLoanDateOption() {
return this.loanDateOption;
}
public LocalDate getLoanFromDate() {
return this.loanFromDate;
}
public LocalDate getLoanToDate() {
return this.loanToDate;
}
public Boolean getIncludeOutStandingAmountPercentage() {
return this.includeOutStandingAmountPercentage;
}
public String getOutStandingAmountPercentageCondition() {
return this.outStandingAmountPercentageCondition;
}
public BigDecimal getMinOutStandingAmountPercentage() {
return this.minOutStandingAmountPercentage;
}
public BigDecimal getMaxOutStandingAmountPercentage() {
return this.maxOutStandingAmountPercentage;
}
public BigDecimal getOutStandingAmountPercentage() {
return this.outStandingAmountPercentage;
}
public Boolean getIncludeOutstandingAmount() {
return this.includeOutstandingAmount;
}
public String getOutstandingAmountCondition() {
return this.outstandingAmountCondition;
}
public BigDecimal getMinOutstandingAmount() {
return this.minOutstandingAmount;
}
public BigDecimal getMaxOutstandingAmount() {
return this.maxOutstandingAmount;
}
public BigDecimal getOutstandingAmount() {
return this.outstandingAmount;
}
}
@@ -0,0 +1,61 @@
/**
* 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.search.data;
import java.util.Arrays;
import java.util.List;
public interface AdHocQuerySearchConstants {
String AD_HOC_SEARCH_QUERY_RESOURCE_NAME = "adHocQuery";
String localeParamName = "locale";
String dateFormatParamName = "dateFormat";
String entitiesParamName = "entities";
String loanStatusParamName = "loanStatus";
String loanProductsParamName = "loanProducts";
String officesParamName = "offices";
String loanDateOptionParamName = "loanDateOption";
String loanFromDateParamName = "loanFromDate";
String loanToDateParamName = "loanToDate";
String includeOutStandingAmountPercentageParamName = "includeOutStandingAmountPercentage";
String outStandingAmountPercentageConditionParamName = "outStandingAmountPercentageCondition";
String minOutStandingAmountPercentageParamName = "minOutStandingAmountPercentage";
String maxOutStandingAmountPercentageParamName = "maxOutStandingAmountPercentage";
String outStandingAmountPercentageParamName = "outStandingAmountPercentage";
String includeOutstandingAmountParamName = "includeOutstandingAmount";
String outstandingAmountConditionParamName = "outstandingAmountCondition";
String minOutstandingAmountParamName = "minOutstandingAmount";
String maxOutstandingAmountParamName = "maxOutstandingAmount";
String outstandingAmountParamName = "outstandingAmount";
String approvalDateOption = "approvalDate";
String createDateOption = "createdDate";
String disbursalDateOption = "disbursalDate";
String allLoanStatusOption = "all";
String activeLoanStatusOption = "active";
String overpaidLoanStatusOption = "overpaid";
String arrearsLoanStatusOption = "arrears";
String closedLoanStatusOption = "closed";
String writeoffLoanStatusOption = "writeoff";
List<Object> entityTypeOptions = List.copyOf(Arrays.asList("clients", "groups", "loans", "clientIdentifiers"));
}
@@ -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.search.data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Immutable data object representing datatable data.
*/
@Data
@NoArgsConstructor
public final class AdvancedQueryData implements Serializable {
private List<ColumnFilterData> columnFilters;
private List<String> resultColumns;
public List<ColumnFilterData> getNonNullFilters() {
return columnFilters == null ? new ArrayList<>() : columnFilters;
}
public boolean hasFilter() {
return columnFilters != null && !columnFilters.isEmpty();
}
public List<String> getNonNullResultColumns() {
return resultColumns == null ? new ArrayList<>() : resultColumns;
}
public boolean hasResultColumn() {
return resultColumns != null && !resultColumns.isEmpty();
}
}
@@ -0,0 +1,46 @@
/**
* 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.search.data;
import java.io.Serializable;
import java.util.List;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Immutable data object representing datatable data.
*/
@Data
@NoArgsConstructor
public final class AdvancedQueryRequest implements Serializable {
private AdvancedQueryData baseQuery;
private List<TableQueryData> datatableQueries;
public boolean hasFilter() {
return (baseQuery != null && baseQuery.hasFilter())
|| (datatableQueries != null && datatableQueries.stream().anyMatch(TableQueryData::hasFilter));
}
public boolean hasResultColumn() {
return (baseQuery != null && baseQuery.hasResultColumn())
|| (datatableQueries != null && datatableQueries.stream().anyMatch(TableQueryData::hasResultColumn));
}
}
@@ -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.search.data;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
/**
* Immutable data object representing datatable data.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public final class ColumnFilterData implements Serializable {
private String column;
private List<FilterData> filters;
public static ColumnFilterData eq(String column, String value) {
return new ColumnFilterData(column, List.of(FilterData.eq(value)));
}
public static ColumnFilterData btw(String column, String value1, String value2) {
return new ColumnFilterData(column, List.of(FilterData.btw(value1, value2)));
}
public static ColumnFilterData create(String column, SqlOperator op, String... values) {
return new ColumnFilterData(column, List.of(FilterData.create(op, values)));
}
}
@@ -0,0 +1,54 @@
/**
* 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.search.data;
import static org.apache.fineract.infrastructure.core.service.database.SqlOperator.BTW;
import static org.apache.fineract.infrastructure.core.service.database.SqlOperator.EQ;
import java.io.Serializable;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
/**
* Immutable data object representing datatable data.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public final class FilterData implements Serializable {
private SqlOperator operator;
private List<String> values;
static FilterData eq(String value) {
return new FilterData(EQ, List.of(value));
}
static FilterData btw(String value1, String value2) {
return new FilterData(BTW, List.of(value1, value2));
}
static FilterData create(SqlOperator op, String... values) {
return new FilterData(op, List.of(values));
}
}
@@ -0,0 +1,103 @@
/**
* 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.search.data;
import org.apache.fineract.portfolio.search.SearchConstants.SearchSupportedResources;
public class SearchConditions {
private final String searchQuery;
private final String searchResource;
private final Boolean clientSearch;
private final Boolean groupSearch;
private final Boolean loanSeach;
private final Boolean savingSeach;
private final Boolean shareSeach;
private final Boolean clientIdentifierSearch;
private Boolean exactMatch;
public SearchConditions(final String searchQueryParam, final String searchResource, Boolean exactMatch) {
this.searchQuery = searchQueryParam;
this.searchResource = searchResource;
this.exactMatch = exactMatch;
this.clientSearch = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTS.name().toLowerCase());
this.groupSearch = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.GROUPS.name().toLowerCase());
this.loanSeach = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.LOANS.name().toLowerCase());
this.savingSeach = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.SAVINGS.name().toLowerCase());
this.shareSeach = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.SHARES.name().toLowerCase());
this.clientIdentifierSearch = null == searchResource
|| searchResource.toLowerCase().contains(SearchSupportedResources.CLIENTIDENTIFIERS.name().toLowerCase());
}
public SearchConditions(final String searchQueryParam, final String searchResource, final Boolean clientSearch,
final Boolean groupSearch, final Boolean loanSeach, final Boolean savingSeach, final Boolean shareSeach,
final Boolean clientIdentifierSearch, Boolean exactMatch) {
this.searchQuery = searchQueryParam;
this.searchResource = searchResource;
this.clientSearch = clientSearch;
this.groupSearch = groupSearch;
this.loanSeach = loanSeach;
this.savingSeach = savingSeach;
this.shareSeach = shareSeach;
this.clientIdentifierSearch = clientIdentifierSearch;
this.exactMatch = exactMatch;
}
public String getSearchQuery() {
return this.searchQuery;
}
public String getSearchResource() {
return this.searchResource;
}
public Boolean getExactMatch() {
return this.exactMatch;
}
public Boolean isClientSearch() {
return this.clientSearch;
}
public Boolean isGroupSearch() {
return this.groupSearch;
}
public Boolean isLoanSeach() {
return this.loanSeach;
}
public Boolean isSavingSeach() {
return this.savingSeach;
}
public Boolean isShareSeach() {
return this.shareSeach;
}
public Boolean isClientIdentifierSearch() {
return this.clientIdentifierSearch;
}
}
@@ -0,0 +1,98 @@
/**
* 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.search.data;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
public class SearchData {
private final Long entityId;
private final String entityAccountNo;
private final String entityExternalId;
private final String entityName;
private final String entityType;
private final Long parentId;
private final String parentName;
private final String entityMobileNo;
private final EnumOptionData entityStatus;
private final String parentType;
private final String subEntityType;
public SearchData(final Long entityId, final String entityAccountNo, final String entityExternalId, final String entityName,
final String entityType, final Long parentId, final String parentName, final String parentType, final String entityMobileNo,
final EnumOptionData entityStatus, final String subEntityType) {
this.entityId = entityId;
this.entityAccountNo = entityAccountNo;
this.entityExternalId = entityExternalId;
this.entityName = entityName;
this.entityType = entityType;
this.parentId = parentId;
this.parentName = parentName;
this.parentType = parentType;
this.entityMobileNo = entityMobileNo;
this.entityStatus = entityStatus;
this.subEntityType = subEntityType;
}
public Long getEntityId() {
return this.entityId;
}
public String getEntityAccountNo() {
return this.entityAccountNo;
}
public String getEntityExternalId() {
return this.entityExternalId;
}
public String getEntityName() {
return this.entityName;
}
public String getEntityType() {
return this.entityType;
}
public Long getParentId() {
return this.parentId;
}
public String getParentName() {
return this.parentName;
}
public String getParentType() {
return this.parentType;
}
public String getEntityMobileNo() {
return this.entityMobileNo;
}
public EnumOptionData getEntityStatus() {
return this.entityStatus;
}
public String getSubEntityType() {
return this.subEntityType;
}
}
@@ -0,0 +1,43 @@
/**
* 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.search.data;
import java.io.Serializable;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* Immutable data object representing datatable data.
*/
@Data
@NoArgsConstructor
public final class TableQueryData implements Serializable {
private String table;
private AdvancedQueryData query;
public boolean hasFilter() {
return query != null && query.hasFilter();
}
public boolean hasResultColumn() {
return query != null && query.hasResultColumn();
}
}
@@ -0,0 +1,106 @@
/**
* 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.search.data;
import static org.apache.fineract.portfolio.search.service.SearchUtil.DEFAULT_PAGE_SIZE;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Locale;
import lombok.Getter;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
@Getter
public class TransactionSearchRequest {
private Long accountId;
private LocalDate fromDate;
private LocalDate toDate;
private LocalDate fromSubmittedDate;
private LocalDate toSubmittedDate;
private BigDecimal fromAmount;
private BigDecimal toAmount;
private String[] types;
private Boolean credit;
private Boolean debit;
private PageRequest pageable;
public TransactionSearchRequest accountId(Long accountId) {
this.accountId = accountId;
return this;
}
public TransactionSearchRequest fromDate(String fromDate, String dateFormat, Locale locale) {
this.fromDate = fromDate == null ? null : DateUtils.parseLocalDate(fromDate, dateFormat, locale);
return this;
}
public TransactionSearchRequest toDate(String toDate, String dateFormat, Locale locale) {
this.toDate = toDate == null ? null : DateUtils.parseLocalDate(toDate, dateFormat, locale);
return this;
}
public TransactionSearchRequest fromSubmittedDate(String fromSubmittedDate, String dateFormat, Locale locale) {
this.fromSubmittedDate = fromSubmittedDate == null ? null : DateUtils.parseLocalDate(fromSubmittedDate, dateFormat, locale);
return this;
}
public TransactionSearchRequest toSubmittedDate(String toSubmittedDate, String dateFormat, Locale locale) {
this.toSubmittedDate = toSubmittedDate == null ? null : DateUtils.parseLocalDate(toSubmittedDate, dateFormat, locale);
return this;
}
public TransactionSearchRequest fromAmount(BigDecimal fromAmount) {
this.fromAmount = fromAmount;
return this;
}
public TransactionSearchRequest toAmount(BigDecimal toAmount) {
this.toAmount = toAmount;
return this;
}
public TransactionSearchRequest types(String types) {
this.types = types == null ? null : types.split(",");
return this;
}
public TransactionSearchRequest credit(Boolean credit) {
this.credit = credit;
return this;
}
public TransactionSearchRequest debit(Boolean debit) {
this.debit = debit;
return this;
}
public TransactionSearchRequest pageable(Integer offset, Integer limit, String orderByProps, Sort.Direction direction) {
offset = MathUtil.nullToDefault(offset, 0);
limit = MathUtil.nullToDefault(limit, DEFAULT_PAGE_SIZE);
String[] properties = Strings.isEmpty(orderByProps) ? null : orderByProps.split(",");
this.pageable = properties == null ? PageRequest.of(offset, limit) : PageRequest.of(offset, limit, direction, properties);
return this;
}
}
@@ -0,0 +1,321 @@
/**
* 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.search.service;
import static java.util.Locale.ENGLISH;
import static org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterErrorWithValue;
import static org.apache.fineract.infrastructure.core.service.DateUtils.DEFAULT_DATETIME_FORMAT;
import static org.apache.fineract.infrastructure.core.service.DateUtils.DEFAULT_DATE_FORMAT;
import static org.apache.fineract.infrastructure.dataqueries.api.DataTableApiConstant.API_FIELD_MANDATORY;
import static org.apache.fineract.portfolio.search.SearchConstants.API_PARAM_COLUMN;
import com.google.gson.JsonObject;
import jakarta.validation.constraints.NotNull;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.core.service.database.JdbcJavaType;
import org.apache.fineract.infrastructure.core.service.database.SqlOperator;
import org.apache.fineract.infrastructure.dataqueries.data.ResultsetColumnHeaderData;
import org.apache.fineract.infrastructure.security.utils.SQLInjectionValidator;
import org.apache.fineract.portfolio.search.data.ColumnFilterData;
import org.apache.fineract.portfolio.search.data.FilterData;
import org.springframework.jdbc.support.rowset.SqlRowSet;
public final class SearchUtil {
public static final int DEFAULT_PAGE_SIZE = 50;
private static final JsonParserHelper helper = new JsonParserHelper();
private SearchUtil() {}
@NotNull
public static Map<String, ResultsetColumnHeaderData> mapHeadersToName(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders) {
return columnHeaders.stream().collect(Collectors.toMap(ResultsetColumnHeaderData::getColumnName, e -> e));
}
public static ResultsetColumnHeaderData findFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) {
return columnHeaders.stream().filter(filter).findFirst().orElse(null);
}
public static ResultsetColumnHeaderData getFiltered(@NotNull Collection<ResultsetColumnHeaderData> columnHeaders,
@NotNull Predicate<ResultsetColumnHeaderData> filter) {
ResultsetColumnHeaderData filtered = findFiltered(columnHeaders, filter);
if (filtered == null) {
throw new PlatformDataIntegrityException("error.msg.column.not.exists", "Column filtered does not exist");
}
return filtered;
}
public static void extractJsonResult(@NotNull SqlRowSet rowSet, @NotNull List<String> selectColumns,
@NotNull List<String> resultColumns, @NotNull List<JsonObject> results) {
JsonObject json = new JsonObject();
for (int i = 0; i < selectColumns.size(); i++) {
Object rowValue = rowSet.getObject(selectColumns.get(i));
if (rowValue != null) {
String rCol = resultColumns.get(i);
if (rowValue instanceof Character) {
json.addProperty(rCol, (Character) rowValue);
} else if (rowValue instanceof Number) {
json.addProperty(rCol, new BigDecimal(rowValue.toString()));
} else if (rowValue instanceof Boolean) {
json.addProperty(rCol, (Boolean) rowValue);
} else if (rowValue instanceof LocalDateTime) {
json.addProperty(rCol, DateUtils.format((LocalDateTime) rowValue));
} else if (rowValue instanceof Timestamp) {
json.addProperty(rCol, DateUtils.format(((Timestamp) rowValue).toLocalDateTime()));
} else if (rowValue instanceof LocalDate) {
json.addProperty(rCol, DateUtils.format((LocalDate) rowValue));
} else if (rowValue instanceof Date) {
json.addProperty(rCol, DateUtils.format(((Date) rowValue).toLocalDate()));
} else {
json.addProperty(rCol, rowValue.toString());
}
}
}
if (json.size() > 0) {
results.add(json);
}
}
@NotNull
public static List<String> validateToJdbcColumnNames(List<String> columns, Map<String, ResultsetColumnHeaderData> headersByName,
boolean allowEmpty) {
List<ResultsetColumnHeaderData> columnHeaders = validateToJdbcColumns(columns, headersByName, allowEmpty);
return columnHeaders.stream().map(e -> e == null ? null : e.getColumnName()).toList();
}
@NotNull
public static List<ResultsetColumnHeaderData> validateToJdbcColumns(List<String> columns,
Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>();
List<ResultsetColumnHeaderData> result = new ArrayList<>();
if (columns == null || columns.isEmpty()) {
if (!allowEmpty) {
errors.add(parameterErrorWithValue("error.msg.columns.empty", "Columns list is empty", API_PARAM_COLUMN, null));
}
} else {
columns.forEach(rcn -> result.add(validateToJdbcColumnImpl(rcn, headersByName, errors, allowEmpty)));
}
if (!errors.isEmpty()) {
throw new PlatformApiDataValidationException(errors);
}
return result;
}
public static String validateToJdbcColumnName(String column, Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
ResultsetColumnHeaderData columnHeader = validateToJdbcColumn(column, headersByName, allowEmpty);
return columnHeader == null ? null : columnHeader.getColumnName();
}
public static ResultsetColumnHeaderData validateToJdbcColumn(String column,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, boolean allowEmpty) {
final List<ApiParameterError> errors = new ArrayList<>();
ResultsetColumnHeaderData columnHeader = validateToJdbcColumnImpl(column, headersByName, errors, allowEmpty);
if (!errors.isEmpty()) {
throw new PlatformApiDataValidationException(errors);
}
return columnHeader;
}
private static ResultsetColumnHeaderData validateToJdbcColumnImpl(String column,
@NotNull Map<String, ResultsetColumnHeaderData> headersByName, @NotNull List<ApiParameterError> errors, boolean allowEmpty) {
if (!allowEmpty && column == null) {
errors.add(parameterErrorWithValue("error.msg.column.empty", "Column filter is empty", API_PARAM_COLUMN, null));
}
if (column == null) {
return null;
}
Collection<ResultsetColumnHeaderData> columnHeaders = headersByName.values();
ResultsetColumnHeaderData columnHeader;
if ((columnHeader = findFiltered(columnHeaders, e -> e.isNamed(column))) == null
&& (columnHeader = findFiltered(columnHeaders, e -> e.isNamed(column.replaceAll(" ", "_")))) == null
&& (columnHeader = findFiltered(columnHeaders, e -> e.isNamed(camelToSnake(column.replaceAll(" ", ""))))) == null) {
errors.add(parameterErrorWithValue("error.msg.invalid.column", "Column not exist in database", API_PARAM_COLUMN, column));
}
return columnHeader;
}
public static boolean buildQueryCondition(List<ColumnFilterData> columnFilters, @NotNull StringBuilder where,
@NotNull List<Object> params, String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat,
String dateTimeFormat, Locale locale, boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
if (columnFilters == null) {
return false;
}
boolean added = false;
int isize = columnFilters.size();
for (int i = 0; i < isize; i++) {
boolean addedFilter = buildFilterCondition(columnFilters.get(i), where, params, alias, headersByName, dateFormat,
dateTimeFormat, locale, embedded, sqlGenerator);
if (addedFilter && i < isize - 1) {
where.append(" AND ");
}
added |= addedFilter;
}
return added;
}
public static boolean buildFilterCondition(ColumnFilterData columnFilter, @NotNull StringBuilder where, @NotNull List<Object> params,
String alias, Map<String, ResultsetColumnHeaderData> headersByName, String dateFormat, String dateTimeFormat, Locale locale,
boolean embedded, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
String columnName = columnFilter.getColumn();
List<FilterData> filters = columnFilter.getFilters();
int size = filters.size();
for (int i = 0; i < size; i++) {
if (!embedded && where.isEmpty()) {
where.append(" WHERE ");
}
ResultsetColumnHeaderData columnHeader = validateToJdbcColumn(columnName, headersByName, false);
FilterData filter = filters.get(i);
SqlOperator operator = filter.getOperator();
List<String> values = filter.getValues();
List<Object> objectValues = values == null ? null
: values.stream()
.map(e -> parseJdbcColumnValue(columnHeader, e, dateFormat, dateTimeFormat, locale, false, sqlGenerator))
.toList();
buildCondition(columnHeader.getColumnName(), columnHeader.getColumnType(), operator, objectValues, where, params, alias,
sqlGenerator);
if (i < size - 1) {
where.append(" AND ");
}
}
return size > 0;
}
public static void buildCondition(@NotNull String definition, JdbcJavaType columnType, @NotNull SqlOperator operator,
List<Object> values, @NotNull StringBuilder where, @NotNull List<Object> params, String alias,
@NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
int paramCount = values == null ? 0 : values.size();
where.append(operator.formatPlaceholder(sqlGenerator, definition, paramCount, alias));
if (values != null) {
params.addAll(values);
}
}
public static Object parseJdbcColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat,
String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
return columnHeader.getColumnType().toJdbcValue(sqlGenerator.getDialect(),
parseColumnValue(columnHeader, columnValue, dateFormat, dateTimeFormat, locale, strict, sqlGenerator), false);
}
public static Object parseColumnValue(@NotNull ResultsetColumnHeaderData columnHeader, String columnValue, String dateFormat,
String dateTimeFormat, Locale locale, boolean strict, @NotNull DatabaseSpecificSQLGenerator sqlGenerator) {
JdbcJavaType colType = columnHeader.getColumnType();
if (!colType.isStringType() || !columnHeader.isMandatory()) {
columnValue = StringUtils.trimToNull(columnValue);
}
String errorCode = "validation.msg.validation.errors.exist";
String errorMsg = "Validation errors exist.";
if (columnValue == null && columnHeader.isMandatory()) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.column.mandatory", API_FIELD_MANDATORY,
columnHeader.getColumnName());
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
if (StringUtils.isEmpty(columnValue)) {
return columnValue;
}
if (strict) {
SQLInjectionValidator.validateDynamicQuery(columnValue);
}
if (columnHeader.hasColumnValues()) {
if (columnHeader.isCodeValueDisplayType()) {
if (!columnHeader.isColumnValueAllowed(columnValue)) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return columnValue;
} else if (columnHeader.isCodeLookupDisplayType()) {
final Integer codeLookup = Integer.valueOf(columnValue);
if (!columnHeader.isColumnCodeAllowed(codeLookup)) {
ApiParameterError error = ApiParameterError.parameterError("error.msg.invalid.columnValue",
"Value not found in Allowed Value list", columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return codeLookup;
} else {
throw new PlatformDataIntegrityException("error.msg.invalid.columnType.", "Code: " + columnHeader.getColumnName()
+ " - Invalid Type " + colType.getJdbcName(sqlGenerator.getDialect()) + " (neither varchar nor int)");
}
}
locale = locale == null ? ENGLISH : locale;
if (colType.isDateType()) {
String format = dateFormat == null ? DEFAULT_DATE_FORMAT : dateFormat;
return JsonParserHelper.convertFrom(columnValue, columnHeader.getColumnName(), format, locale);
}
if (colType.isDateTimeType()) {
String format = dateTimeFormat == null ? DEFAULT_DATETIME_FORMAT : dateTimeFormat;
return JsonParserHelper.convertDateTimeFrom(columnValue, columnHeader.getColumnName(), format, locale);
}
if (colType.isAnyIntegerType()) {
return helper.convertToInteger(columnValue, columnHeader.getColumnName(), locale);
}
if (colType.isDecimalType()) {
return helper.convertFrom(columnValue, columnHeader.getColumnName(), locale);
}
if (colType.isBooleanType()) {
final Boolean boolValue = BooleanUtils.toBooleanObject(columnValue);
if (boolValue == null) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.invalid.boolean.format",
"The parameter " + columnHeader.getColumnName() + " has value: " + columnValue + " which is invalid boolean value.",
columnHeader.getColumnName(), columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
return boolValue;
}
if (colType.isStringType()) {
if (columnHeader.getColumnLength() > 0 && columnValue.length() > columnHeader.getColumnLength()) {
ApiParameterError error = ApiParameterError.parameterError("validation.msg.datatable.entry.column.exceeds.maxlength",
"The column `" + columnHeader.getColumnName() + "` exceeds its defined max-length ", columnHeader.getColumnName(),
columnValue);
throw new PlatformApiDataValidationException(errorCode, errorMsg, List.of(error));
}
}
return columnValue;
}
public static String camelToSnake(final String camelStr) {
return camelStr == null ? null
: camelStr.replaceAll("([A-Z]+)([A-Z][a-z])", "$1_$2").replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase();
}
}
@@ -0,0 +1,26 @@
/**
* 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.tax.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TaxComponentRepository extends JpaRepository<TaxComponent, Long>, JpaSpecificationExecutor<TaxComponent> {
}
@@ -0,0 +1,39 @@
/**
* 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.tax.domain;
import org.apache.fineract.portfolio.tax.exception.TaxComponentNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TaxComponentRepositoryWrapper {
private final TaxComponentRepository repository;
@Autowired
public TaxComponentRepositoryWrapper(final TaxComponentRepository repository) {
this.repository = repository;
}
public TaxComponent findOneWithNotFoundDetection(final Long id) {
return this.repository.findById(id).orElseThrow(() -> new TaxComponentNotFoundException(id));
}
}
@@ -0,0 +1,26 @@
/**
* 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.tax.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TaxGroupRepository extends JpaRepository<TaxGroup, Long>, JpaSpecificationExecutor<TaxGroup> {
}
@@ -0,0 +1,38 @@
/**
* 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.tax.domain;
import org.apache.fineract.portfolio.tax.exception.TaxGroupNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class TaxGroupRepositoryWrapper {
private final TaxGroupRepository repository;
@Autowired
public TaxGroupRepositoryWrapper(final TaxGroupRepository repository) {
this.repository = repository;
}
public TaxGroup findOneWithNotFoundDetection(final Long id) {
return this.repository.findById(id).orElseThrow(() -> new TaxGroupNotFoundException(id));
}
}
@@ -0,0 +1,28 @@
/**
* 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.tax.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class TaxComponentNotFoundException extends AbstractPlatformResourceNotFoundException {
public TaxComponentNotFoundException(final Long id) {
super("error.msg.tax.component.id.invalid", "tax component with identifier " + id + " does not exist", id);
}
}
@@ -0,0 +1,28 @@
/**
* 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.tax.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class TaxGroupNotFoundException extends AbstractPlatformResourceNotFoundException {
public TaxGroupNotFoundException(final Long id) {
super("error.msg.tax.group.id.invalid", "tax group with identifier " + id + " does not exist", id);
}
}
@@ -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.tax.service;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.tax.data.TaxComponentData;
import org.apache.fineract.portfolio.tax.data.TaxGroupMappingsData;
import org.apache.fineract.portfolio.tax.domain.TaxComponent;
import org.apache.fineract.portfolio.tax.domain.TaxGroupMappings;
public final class TaxUtils {
private TaxUtils() {
}
public static Map<TaxComponent, BigDecimal> splitTax(final BigDecimal amount, final LocalDate date,
final Set<TaxGroupMappings> taxGroupMappings, final int scale) {
Map<TaxComponent, BigDecimal> map = new HashMap<>(3);
if (amount != null) {
final double amountVal = amount.doubleValue();
double cent_percentage = Double.parseDouble("100.0");
for (TaxGroupMappings groupMappings : taxGroupMappings) {
if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) {
TaxComponent component = groupMappings.getTaxComponent();
BigDecimal percentage = component.getApplicablePercentage(date);
if (percentage != null) {
double percentageVal = percentage.doubleValue();
double tax = amountVal * percentageVal / cent_percentage;
map.put(component, BigDecimal.valueOf(tax).setScale(scale, MoneyHelper.getRoundingMode()));
}
}
}
}
return map;
}
public static Map<TaxComponentData, BigDecimal> splitTaxData(final BigDecimal amount, final LocalDate date,
final Set<TaxGroupMappingsData> taxGroupMappings, final int scale) {
Map<TaxComponentData, BigDecimal> map = new HashMap<>(3);
if (amount != null) {
final double amountVal = amount.doubleValue();
double cent_percentage = Double.parseDouble("100.0");
for (TaxGroupMappingsData groupMappings : taxGroupMappings) {
if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) {
TaxComponentData component = groupMappings.getTaxComponent();
BigDecimal percentage = component.getApplicablePercentage(date);
if (percentage != null) {
double percentageVal = percentage.doubleValue();
double tax = amountVal * percentageVal / cent_percentage;
map.put(component, BigDecimal.valueOf(tax).setScale(scale, MoneyHelper.getRoundingMode()));
}
}
}
}
return map;
}
public static BigDecimal incomeAmount(final BigDecimal amount, final LocalDate date, final Set<TaxGroupMappings> taxGroupMappings,
final int scale) {
Map<TaxComponent, BigDecimal> map = splitTax(amount, date, taxGroupMappings, scale);
return incomeAmount(amount, map);
}
public static BigDecimal incomeAmount(final BigDecimal amount, final Map<TaxComponent, BigDecimal> map) {
BigDecimal totalTax = totalTaxAmount(map);
return amount.subtract(totalTax);
}
public static BigDecimal totalTaxAmount(final Map<TaxComponent, BigDecimal> map) {
BigDecimal totalTax = BigDecimal.ZERO;
for (BigDecimal tax : map.values()) {
totalTax = totalTax.add(tax);
}
return totalTax;
}
public static BigDecimal totalTaxDataAmount(final Map<TaxComponentData, BigDecimal> map) {
BigDecimal totalTax = BigDecimal.ZERO;
for (BigDecimal tax : map.values()) {
totalTax = totalTax.add(tax);
}
return totalTax;
}
public static BigDecimal addTax(final BigDecimal amount, final LocalDate date, final List<TaxGroupMappings> taxGroupMappings,
final int scale) {
BigDecimal totalAmount = null;
if (amount != null && amount.compareTo(BigDecimal.ZERO) > 0) {
double percentageVal = 0;
double amountVal = amount.doubleValue();
double cent_percentage = Double.parseDouble("100.0");
for (TaxGroupMappings groupMappings : taxGroupMappings) {
if (groupMappings.occursOnDayFromAndUpToAndIncluding(date)) {
TaxComponent component = groupMappings.getTaxComponent();
BigDecimal percentage = component.getApplicablePercentage(date);
if (percentage != null) {
percentageVal = percentageVal + percentage.doubleValue();
}
}
}
double total = amountVal * cent_percentage / (cent_percentage - percentageVal);
totalAmount = BigDecimal.valueOf(total).setScale(scale, MoneyHelper.getRoundingMode());
}
return totalAmount;
}
}