update current product code and RD code

This commit is contained in:
Nitesh Yadav
2026-04-15 20:34:12 +05:30
parent d7821c7824
commit 2facd3744d
48 changed files with 32602 additions and 1551 deletions
+68
View File
@@ -0,0 +1,68 @@
/**
* 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.
*/
description = 'Fineract Current Product'
compileJava {
dependsOn ':fineract-avro-schemas:buildJavaSdk'
}
configurations {
providedRuntime // needed for Spring Boot executable WAR
providedCompile
compile() {
exclude module: 'hibernate-entitymanager'
exclude module: 'hibernate-validator'
exclude module: 'activation'
exclude module: 'bcmail-jdk14'
exclude module: 'bcprov-jdk14'
exclude module: 'bctsp-jdk14'
exclude module: 'c3p0'
exclude module: 'stax-api'
exclude module: 'jaxb-api'
exclude module: 'jaxb-impl'
exclude module: 'jboss-logging'
exclude module: 'itext-rtf'
exclude module: 'classworlds'
}
runtime
}
apply from: 'dependencies.gradle'
// If we are running Gradle within Eclipse to enhance classes with OpenJPA,
// set the classes directory to point to Eclipse's default build directory
if (project.hasProperty('env') && project.getProperty('env') == 'eclipse') {
sourceSets.main.java.outputDir = new File(rootProject.projectDir, "fineract-current-product/bin/main")
}
eclipse {
project {
buildCommand([ LaunchConfigHandle: "<project>/.externalToolBuilders/OpenJPA Enhance Builder.launch" ], 'org.eclipse.ui.externaltools.ExternalToolBuilder')
}
}
if (!(project.hasProperty('env') && project.getProperty('env') == 'dev')) {
sourceSets {
test {
java {
exclude '**/core/boot/tests/**'
}
}
}
}
@@ -0,0 +1,64 @@
/**
* 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.
*/
dependencies {
// Never use "compile" scope, but make all dependencies either 'implementation', 'runtimeOnly' or 'testCompile'.
implementation(project(path: ':fineract-core'))
implementation(project(path: ':fineract-accounting'))
implementation(project(path: ':fineract-charge'))
implementation(project(path: ':fineract-tax'))
implementation(project(path: ':fineract-savings'))
implementation(
'org.springframework.boot:spring-boot-starter-web',
'org.springframework.boot:spring-boot-starter-security',
'jakarta.ws.rs:jakarta.ws.rs-api',
'com.google.guava:guava',
'com.google.code.gson:gson',
'org.apache.commons:commons-lang3',
'com.jayway.jsonpath:json-path',
'com.github.spotbugs:spotbugs-annotations',
'io.swagger.core.v3:swagger-annotations-jakarta',
'org.mapstruct:mapstruct',
)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.mapstruct:mapstruct-processor'
implementation ('org.springframework.boot:spring-boot-starter-data-jpa') {
exclude group: 'org.hibernate'
}
implementation('org.eclipse.persistence:org.eclipse.persistence.jpa') {
exclude group: 'org.eclipse.persistence', module: 'jakarta.persistence'
}
testImplementation( 'io.github.classgraph:classgraph' )
testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'com.jayway.jsonpath', module: 'json-path'
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
exclude group: 'jakarta.activation'
exclude group: 'javax.activation'
exclude group: 'org.skyscreamer'
}
testImplementation ('org.mockito:mockito-inline')
}
@@ -0,0 +1,460 @@
/**
* 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.currentproduct.data;
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeAmountParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.feeOnMonthDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import com.google.gson.JsonElement;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.time.MonthDay;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingParams;
import org.apache.fineract.accounting.common.AccountingValidations;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.savings.data.SavingsProductAccountingDataValidator;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class CurrentProductDataValidator {
private final FromJsonHelper fromApiJsonHelper;
private final SavingsProductAccountingDataValidator savingsProductAccountingDataValidator;
private static final Set<String> CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS = new HashSet<>(Arrays.asList(
SavingsApiConstants.localeParamName, SavingsApiConstants.dateFormatParamName,
SavingsApiConstants.monthDayFormatParamName, nameParamName, shortNameParamName,
descriptionParamName, currencyCodeParamName, digitsAfterDecimalParamName, inMultiplesOfParamName,
nominalAnnualInterestRateParamName, interestCompoundingPeriodTypeParamName,
interestPostingPeriodTypeParamName, interestCalculationTypeParamName,
interestCalculationDaysInYearTypeParamName, minRequiredOpeningBalanceParamName,
lockinPeriodFrequencyParamName, lockinPeriodFrequencyTypeParamName,
SavingsApiConstants.withdrawalFeeAmountParamName, SavingsApiConstants.withdrawalFeeTypeParamName,
withdrawalFeeForTransfersParamName, feeAmountParamName, feeOnMonthDayParamName,
SavingsApiConstants.accountingRuleParamName, SavingsApiConstants.chargesParamName,
SavingsApiConstants.otherGlMappingsParamName,
SavingProductAccountingParams.INCOME_FROM_FEES.getValue(),
SavingProductAccountingParams.INCOME_FROM_PENALTIES.getValue(),
SavingProductAccountingParams.INTEREST_ON_SAVINGS.getValue(),
SavingProductAccountingParams.PENALTIES_RECEIVABLE.getValue(),
SavingProductAccountingParams.PAYMENT_CHANNEL_FUND_SOURCE_MAPPING.getValue(),
SavingProductAccountingParams.SAVINGS_CONTROL.getValue(),
SavingProductAccountingParams.TRANSFERS_SUSPENSE.getValue(),
SavingProductAccountingParams.SAVINGS_REFERENCE.getValue(),
SavingProductAccountingParams.FEE_INCOME_ACCOUNT_MAPPING.getValue(),
SavingProductAccountingParams.PENALTY_INCOME_ACCOUNT_MAPPING.getValue(),
SavingProductAccountingParams.FEES_RECEIVABLE.getValue(),
SavingProductAccountingParams.INTEREST_PAYABLE.getValue(),
SavingProductAccountingParams.OVERDRAFT_PORTFOLIO_CONTROL.getValue(),
SavingProductAccountingParams.LOSSES_WRITTEN_OFF.getValue(),
SavingProductAccountingParams.INCOME_FROM_INTEREST.getValue(),
SavingProductAccountingParams.ESCHEAT_LIABILITY.getValue(),
SavingProductAccountingParams.INTEREST_RECEIVABLE.getValue(),
isDormancyTrackingActiveParamName, daysToDormancyParamName, daysToInactiveParamName,
daysToEscheatParamName, allowOverdraftParamName, overdraftLimitParamName,
nominalAnnualInterestRateOverdraftParamName, minOverdraftForInterestCalculationParamName,
SavingsApiConstants.minRequiredBalanceParamName, SavingsApiConstants.enforceMinRequiredBalanceParamName,
SavingsApiConstants.maxAllowedLienLimitParamName, SavingsApiConstants.lienAllowedParamName,
minBalanceForInterestCalculationParamName, withHoldTaxParamName, taxGroupIdParamName,
SavingsApiConstants.accountTypeParamName, SavingsApiConstants.customerSegmentParamName,
SavingsApiConstants.productCodeParamName, SavingsApiConstants.effectiveDateParamName,
SavingsApiConstants.effectiveToParamName, SavingsApiConstants.endDateParamName,
SavingsApiConstants.introducerRequiredParamName, SavingsApiConstants.shareLinkingEnabledParamName,
SavingsApiConstants.nominationFacilityParamName, SavingsApiConstants.dormancyAlertParamName,
SavingsApiConstants.interestCalculateSelectorParamName, SavingsApiConstants.averagePeriodParamName,
SavingsApiConstants.autoPostParamName, SavingsApiConstants.frequencyFixDayParamName,
SavingsApiConstants.lastInterestDateParamName, SavingsApiConstants.enableLockinPeriodParamName,
SavingsApiConstants.accountFacilityConfigParamName, SavingsApiConstants.accountFacilityLimitsParamName,
SavingsApiConstants.depositLimitKycParamName,
SavingsApiConstants.balanceFrequencyParamName, SavingsApiConstants.accountClosedZeroBalanceInDayClosingParamName,
SavingsApiConstants.balanceSwapOptionParamName, SavingsApiConstants.msmeParamName,
SavingsApiConstants.autoDebitFacilityParamName, SavingsApiConstants.avgBalanceCalcPeriodParamName,
SavingsApiConstants.validForPeriodParamName, SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName));
public void validateForCreate(final String json) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(CURRENT_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
final BigDecimal nominalAnnualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate).zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCompoundingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType)
.isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
}
final Integer interestPostingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestPostingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestPostingPeriodTypeParamName).value(interestPostingPeriodType).notNull()
.isOneOfTheseValues(SavingsPostingInterestPeriodType.integerValues());
if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull();
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationDaysInYearTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType)
.isOneOfTheseValues(SavingsInterestCalculationDaysInYearType.integerValues());
}
if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
baseDataValidator.reset().parameter(descriptionParamName).value(description).ignoreIfNull().notExceedingLengthOf(500);
}
if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element);
baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).integerZeroOrGreater();
if (lockinPeriodFrequency != null) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).notNull().inMinMaxRange(0, 3);
}
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
if (lockinPeriodFrequencyType != null) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).notNull().integerZeroOrGreater();
}
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(withdrawalFeeForTransfersParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers).ignoreIfNull().validateForBooleanValue();
}
// dormancy
Boolean isDormancyActive = this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element);
if (isDormancyActive == null) {
isDormancyActive = false;
}
if (isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element);
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element);
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if (null != daysToInact && null != daysToDor && null != daysToEsc) {
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
}
if (this.fromApiJsonHelper.parameterExists(SavingsApiConstants.effectiveToParamName, element)
&& this.fromApiJsonHelper.parameterExists(SavingsApiConstants.effectiveDateParamName, element)) {
final java.time.LocalDate effectiveDate = this.fromApiJsonHelper.extractLocalDateNamed(SavingsApiConstants.effectiveDateParamName, element);
final java.time.LocalDate effectiveTo = this.fromApiJsonHelper.extractLocalDateNamed(SavingsApiConstants.effectiveToParamName, element);
if (effectiveDate != null && effectiveTo != null && effectiveTo.isBefore(effectiveDate)) {
baseDataValidator.reset().parameter(SavingsApiConstants.effectiveToParamName).value(effectiveTo)
.failWithCode("error.msg.effectiveto.cannot.be.before.effectivedate");
}
}
final Integer accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
|| AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT, true);
}
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minBalanceForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
validateTaxWithHoldingParams(baseDataValidator, element, true);
validateLienParams(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
public void validateForUpdate(final String json, final CurrentProduct product) {
if (StringUtils.isBlank(json)) {
throw new InvalidJsonException();
}
final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, CURRENT_PRODUCT_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(CURRENT_PRODUCT_RESOURCE_NAME);
final JsonElement element = this.fromApiJsonHelper.parse(json);
if (this.fromApiJsonHelper.parameterExists(nameParamName, element)) {
final String name = this.fromApiJsonHelper.extractStringNamed(nameParamName, element);
baseDataValidator.reset().parameter(nameParamName).value(name).notBlank().notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(shortNameParamName, element)) {
final String shortName = this.fromApiJsonHelper.extractStringNamed(shortNameParamName, element);
baseDataValidator.reset().parameter(shortNameParamName).value(shortName).notBlank().notExceedingLengthOf(4);
}
if (this.fromApiJsonHelper.parameterExists(descriptionParamName, element)) {
final String description = this.fromApiJsonHelper.extractStringNamed(descriptionParamName, element);
baseDataValidator.reset().parameter(descriptionParamName).value(description).notBlank().notExceedingLengthOf(500);
}
if (this.fromApiJsonHelper.parameterExists(currencyCodeParamName, element)) {
final String currencyCode = this.fromApiJsonHelper.extractStringNamed(currencyCodeParamName, element);
baseDataValidator.reset().parameter(currencyCodeParamName).value(currencyCode).notBlank();
}
if (this.fromApiJsonHelper.parameterExists(digitsAfterDecimalParamName, element)) {
final Integer digitsAfterDecimal = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(digitsAfterDecimalParamName, element);
baseDataValidator.reset().parameter(digitsAfterDecimalParamName).value(digitsAfterDecimal).notNull().inMinMaxRange(0, 6);
}
if (this.fromApiJsonHelper.parameterExists(inMultiplesOfParamName, element)) {
final Integer inMultiplesOf = this.fromApiJsonHelper.extractIntegerNamed(inMultiplesOfParamName, element, Locale.getDefault());
baseDataValidator.reset().parameter(inMultiplesOfParamName).value(inMultiplesOf).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateParamName).value(interestRate).notNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(interestCompoundingPeriodTypeParamName, element)) {
final Integer interestCompoundingPeriodType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCompoundingPeriodTypeParamName, element);
baseDataValidator.reset().parameter(interestCompoundingPeriodTypeParamName).value(interestCompoundingPeriodType).notNull()
.isOneOfTheseValues(SavingsCompoundingInterestPeriodType.integerValues());
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationTypeParamName, element)) {
final Integer interestCalculationType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationTypeParamName).value(interestCalculationType).notNull().inMinMaxRange(1, 2);
}
if (this.fromApiJsonHelper.parameterExists(interestCalculationDaysInYearTypeParamName, element)) {
final Integer interestCalculationDaysInYearType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(interestCalculationDaysInYearTypeParamName, element);
baseDataValidator.reset().parameter(interestCalculationDaysInYearTypeParamName).value(interestCalculationDaysInYearType).notNull().isOneOfTheseValues(360, 365);
}
if (this.fromApiJsonHelper.parameterExists(minRequiredOpeningBalanceParamName, element)) {
final BigDecimal minOpeningBalance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minRequiredOpeningBalanceParamName, element);
baseDataValidator.reset().parameter(minRequiredOpeningBalanceParamName).value(minOpeningBalance).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyParamName, element)) {
final Integer lockinPeriodFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(lockinPeriodFrequencyParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyParamName).value(lockinPeriodFrequency).ignoreIfNull().integerZeroOrGreater();
}
if (this.fromApiJsonHelper.parameterExists(lockinPeriodFrequencyTypeParamName, element)) {
final Integer lockinPeriodFrequencyType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(lockinPeriodFrequencyTypeParamName, element);
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(lockinPeriodFrequencyType).inMinMaxRange(0, 3);
}
if (this.fromApiJsonHelper.parameterExists(withdrawalFeeForTransfersParamName, element)) {
final Boolean isWithdrawalFeeApplicableForTransfers = this.fromApiJsonHelper.extractBooleanNamed(withdrawalFeeForTransfersParamName, element);
baseDataValidator.reset().parameter(withdrawalFeeForTransfersParamName).value(isWithdrawalFeeApplicableForTransfers).ignoreIfNull().validateForBooleanValue();
}
Integer accountingRuleType = product.getAccountingType();
if (this.fromApiJsonHelper.parameterExists("accountingRule", element)) {
accountingRuleType = this.fromApiJsonHelper.extractIntegerNamed("accountingRule", element, Locale.getDefault());
baseDataValidator.reset().parameter("accountingRule").value(accountingRuleType).notNull().inMinMaxRange(1, 3);
}
final Boolean isDormancyActive = this.fromApiJsonHelper.parameterExists(isDormancyTrackingActiveParamName, element)
? this.fromApiJsonHelper.extractBooleanNamed(isDormancyTrackingActiveParamName, element)
: product.isDormancyTrackingActive();
if (AccountingValidations.isCashBasedAccounting(accountingRuleType)
|| AccountingValidations.isAccrualPeriodicBasedAccounting(accountingRuleType)) {
savingsProductAccountingDataValidator.evaluateProductAccountingData(accountingRuleType, isDormancyActive, element,
baseDataValidator, DepositAccountType.SAVINGS_DEPOSIT, false);
}
if (null != isDormancyActive && isDormancyActive) {
final Long daysToInact = this.fromApiJsonHelper.parameterExists(daysToInactiveParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToInactiveParamName, element)
: product.getDaysToInactive();
baseDataValidator.reset().parameter(daysToInactiveParamName).value(daysToInact).notNull().longGreaterThanZero();
final Long daysToDor = this.fromApiJsonHelper.parameterExists(daysToDormancyParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToDormancyParamName, element)
: product.getDaysToDormancy();
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).notNull().longGreaterThanZero();
final Long daysToEsc = this.fromApiJsonHelper.parameterExists(daysToEscheatParamName, element)
? this.fromApiJsonHelper.extractLongNamed(daysToEscheatParamName, element)
: product.getDaysToEscheat();
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).notNull().longGreaterThanZero();
if (null != daysToInact && null != daysToDor && null != daysToEsc) {
baseDataValidator.reset().parameter(daysToDormancyParamName).value(daysToDor).longGreaterThanNumber(daysToInact);
baseDataValidator.reset().parameter(daysToEscheatParamName).value(daysToEsc).longGreaterThanNumber(daysToDor);
}
}
validateOverdraftParams(baseDataValidator, element);
if (this.fromApiJsonHelper.parameterExists(minBalanceForInterestCalculationParamName, element)) {
final BigDecimal minBalanceForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minBalanceForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minBalanceForInterestCalculationParamName).value(minBalanceForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
validateTaxWithHoldingParams(baseDataValidator, element, false);
validateLienParams(baseDataValidator, element);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private void validateOverdraftParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(allowOverdraftParamName, element)) {
final Boolean allowOverdraft = this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element);
baseDataValidator.reset().parameter(allowOverdraftParamName).value(allowOverdraft).ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(overdraftLimitParamName, element)) {
final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
baseDataValidator.reset().parameter(overdraftLimitParamName).value(overdraftLimit).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateOverdraftParamName, element)) {
final BigDecimal nominalAnnualInterestRateOverdraft = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateOverdraftParamName, element);
baseDataValidator.reset().parameter(nominalAnnualInterestRateOverdraftParamName).value(nominalAnnualInterestRateOverdraft).ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(minOverdraftForInterestCalculationParamName, element)) {
final BigDecimal minOverdraftForInterestCalculation = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(minOverdraftForInterestCalculationParamName, element);
baseDataValidator.reset().parameter(minOverdraftForInterestCalculationParamName).value(minOverdraftForInterestCalculation).ignoreIfNull().zeroOrPositiveAmount();
}
}
private void validateLienParams(final DataValidatorBuilder baseDataValidator, final JsonElement element) {
if (this.fromApiJsonHelper.parameterExists(lienAllowedParamName, element)) {
final Boolean lienAllowed = this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element);
baseDataValidator.reset().parameter(lienAllowedParamName).value(lienAllowed).ignoreIfNull().validateForBooleanValue();
}
if (this.fromApiJsonHelper.parameterExists(maxAllowedLienLimitParamName, element)) {
final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, element);
baseDataValidator.reset().parameter(maxAllowedLienLimitParamName).value(maxAllowedLienLimit).ignoreIfNull().zeroOrPositiveAmount();
}
if (BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(lienAllowedParamName, element))
&& BooleanUtils.isTrue(this.fromApiJsonHelper.extractBooleanNamed(allowOverdraftParamName, element))) {
final BigDecimal maxAllowedLienLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(maxAllowedLienLimitParamName, element);
final BigDecimal overdraftLimit = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(overdraftLimitParamName, element);
if (overdraftLimit.compareTo(maxAllowedLienLimit) > 0) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("Overdraft.limit.can.not.be.greater.than.lien.limit");
}
}
}
private void validateTaxWithHoldingParams(final DataValidatorBuilder baseDataValidator, final JsonElement element, final boolean isCreate) {
if (this.fromApiJsonHelper.parameterExists(withHoldTaxParamName, element)) {
final String withHoldTax = this.fromApiJsonHelper.extractStringNamed(withHoldTaxParamName, element);
baseDataValidator.reset().parameter(withHoldTaxParamName).value(withHoldTax).ignoreIfNull().validateForBooleanValue();
}
Boolean withHoldTax = this.fromApiJsonHelper.extractBooleanNamed(withHoldTaxParamName, element);
if (withHoldTax == null) {
withHoldTax = false;
}
if (this.fromApiJsonHelper.parameterExists(taxGroupIdParamName, element)) {
final Long taxGroupId = this.fromApiJsonHelper.extractLongNamed(taxGroupIdParamName, element);
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).ignoreIfNull().longGreaterThanZero();
if (withHoldTax) {
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank();
}
} else if (withHoldTax && isCreate) {
final Long taxGroupId = null;
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank();
}
}
}
@@ -0,0 +1,868 @@
/**
* 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.currentproduct.domain;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.localeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.otherGlMappingsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.avgBalanceCalcPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.validForPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName;
import com.google.gson.JsonArray;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Embedded;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "m_current_product", uniqueConstraints = {
@UniqueConstraint(columnNames = { "name" }, name = "cp_unq_name"),
@UniqueConstraint(columnNames = { "short_name" }, name = "cp_unq_short_name") })
public class CurrentProduct extends AbstractPersistableCustom<Long> {
@Column(name = "name", nullable = false, unique = true)
private String name;
@Column(name = "short_name", nullable = false, unique = true)
private String shortName;
@Column(name = "description", length = 500, nullable = false)
private String description;
@Embedded
private MonetaryCurrency currency;
@Column(name = "nominal_annual_interest_rate", scale = 6, precision = 19, nullable = false)
private BigDecimal nominalAnnualInterestRate;
@Column(name = "interest_compounding_period_enum", nullable = false)
private Integer interestCompoundingPeriodType;
@Column(name = "interest_posting_period_enum", nullable = false)
private Integer interestPostingPeriodType;
@Column(name = "interest_calculation_type_enum", nullable = false)
private Integer interestCalculationType;
@Column(name = "interest_calculation_days_in_year_type_enum", nullable = false)
private Integer interestCalculationDaysInYearType;
@Column(name = "min_required_opening_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredOpeningBalance;
@Column(name = "lockin_period_frequency", nullable = true)
private Integer lockinPeriodFrequency;
@Column(name = "lockin_period_frequency_enum", nullable = true)
private Integer lockinPeriodFrequencyType;
@Column(name = "accounting_type", nullable = false)
private Integer accountingRule;
@Column(name = "withdrawal_fee_for_transfer")
private boolean withdrawalFeeApplicableForTransfer;
@ManyToMany
@JoinTable(name = "m_current_product_charge",
joinColumns = @JoinColumn(name = "current_product_id"),
inverseJoinColumns = @JoinColumn(name = "charge_id"))
private Set<Charge> charges;
@Column(name = "allow_overdraft")
private boolean allowOverdraft;
@Column(name = "overdraft_limit", scale = 6, precision = 19, nullable = true)
private BigDecimal overdraftLimit;
@Column(name = "nominal_annual_interest_rate_overdraft", scale = 6, precision = 19, nullable = true)
private BigDecimal nominalAnnualInterestRateOverdraft;
@Column(name = "min_overdraft_for_interest_calculation", scale = 6, precision = 19, nullable = true)
private BigDecimal minOverdraftForInterestCalculation;
@Column(name = "enforce_min_required_balance")
private boolean enforceMinRequiredBalance;
@Column(name = "min_required_balance", scale = 6, precision = 19, nullable = true)
private BigDecimal minRequiredBalance;
@Column(name = "is_lien_allowed", nullable = false)
private boolean lienAllowed;
@Column(name = "max_allowed_lien_limit", scale = 6, precision = 19, nullable = true)
private BigDecimal maxAllowedLienLimit;
@Column(name = "min_balance_for_interest_calculation", scale = 6, precision = 19, nullable = true)
private BigDecimal minBalanceForInterestCalculation;
@Column(name = "withhold_tax", nullable = false)
private boolean withHoldTax;
@ManyToOne
@JoinColumn(name = "tax_group_id")
private TaxGroup taxGroup;
@Column(name = "is_dormancy_tracking_active")
private Boolean isDormancyTrackingActive;
@Column(name = "days_to_inactive")
private Long daysToInactive;
@Column(name = "days_to_dormancy")
private Long daysToDormancy;
@Column(name = "days_to_escheat")
private Long daysToEscheat;
@Lob
@Column(name = "other_gl_mappings")
private String otherGlMappings;
// Enhanced fields
@Column(name = "account_type", length = 50)
private String accountType;
@Column(name = "customer_segment", length = 50)
private String customerSegment;
@Column(name = "product_code", length = 20, unique = true)
private String productCode;
@Column(name = "effective_date")
private LocalDate effectiveDate;
@Column(name = "effective_to")
private LocalDate effectiveTo;
@Column(name = "end_date")
private LocalDate endDate;
@Column(name = "introducer_required")
private boolean introducerRequired = false;
@Column(name = "share_linking_enabled")
private boolean shareLinkingEnabled = false;
@Column(name = "nomination_facility")
private boolean nominationFacility = false;
@Column(name = "dormancy_alert")
private boolean dormancyAlert = false;
@Column(name = "interest_calculate_selector")
private Integer interestCalculateSelector;
@Column(name = "average_period", length = 20)
private String averagePeriod;
@Column(name = "auto_post")
private boolean autoPost = false;
@Column(name = "frequency_fix_day")
private Integer frequencyFixDay;
@Column(name = "last_interest_date")
private LocalDate lastInterestDate;
@Column(name = "enable_lockin_period")
private boolean enableLockinPeriod = false;
@Column(name = "balance_frequency", length = 50)
private String balanceFrequency;
@Column(name = "account_closed_zero_balance_in_day_closing")
private Boolean accountClosedZeroBalanceInDayClosing = false;
// New UI fields (Currency step)
@Column(name = "balance_swap_option")
private boolean balanceSwapOption = false;
@Column(name = "msme")
private boolean msme = false;
@Column(name = "auto_debit_facility")
private boolean autoDebitFacility = false;
// New UI fields (OD section)
@Column(name = "avg_balance_calc_period", length = 10)
private String avgBalanceCalcPeriod;
@Column(name = "valid_for_period", length = 10)
private String validForPeriod;
@Column(name = "od_account_closed_zero_balance_in_day_closing")
private boolean odAccountClosedZeroBalanceInDayClosing = false;
@OneToOne(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private CurrentProductFacilityConfig facilityConfig;
@OneToMany(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private Set<CurrentProductFacilityLimit> facilityLimits = new HashSet<>();
@OneToOne(cascade = CascadeType.ALL, mappedBy = "currentProduct", orphanRemoval = true)
private CurrentProductDepositLimitKyc depositLimitKyc;
public static CurrentProduct createNew(final String name, final String shortName, final String description,
final MonetaryCurrency currency, final BigDecimal interestRate,
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
final SavingsPostingInterestPeriodType interestPostingPeriodType,
final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType,
final Set<Charge> charges, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance,
final boolean lienAllowed, final BigDecimal maxAllowedLienLimit,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup,
final String otherGlMappings, final Boolean isDormancyTrackingActive, final Long daysToInactive,
final Long daysToDormancy, final Long daysToEscheat) {
return new CurrentProduct(name, shortName, description, currency, interestRate, interestCompoundingPeriodType,
interestPostingPeriodType, interestCalculationType, interestCalculationDaysInYearType,
minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeApplicableForTransfer, accountingRuleType, charges, allowOverdraft, overdraftLimit,
enforceMinRequiredBalance, minRequiredBalance, lienAllowed, maxAllowedLienLimit,
minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation,
withHoldTax, taxGroup, otherGlMappings, isDormancyTrackingActive, daysToInactive, daysToDormancy,
daysToEscheat);
}
protected CurrentProduct() {
this.name = null;
this.description = null;
}
private CurrentProduct(final String name, final String shortName, final String description,
final MonetaryCurrency currency, final BigDecimal interestRate,
final SavingsCompoundingInterestPeriodType interestCompoundingPeriodType,
final SavingsPostingInterestPeriodType interestPostingPeriodType,
final SavingsInterestCalculationType interestCalculationType,
final SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType,
final BigDecimal minRequiredOpeningBalance, final Integer lockinPeriodFrequency,
final SavingsPeriodFrequencyType lockinPeriodFrequencyType,
final boolean withdrawalFeeApplicableForTransfer, final AccountingRuleType accountingRuleType,
final Set<Charge> charges, final boolean allowOverdraft, final BigDecimal overdraftLimit,
final boolean enforceMinRequiredBalance, final BigDecimal minRequiredBalance,
final boolean lienAllowed, final BigDecimal maxAllowedLienLimit,
final BigDecimal minBalanceForInterestCalculation, final BigDecimal nominalAnnualInterestRateOverdraft,
final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroup taxGroup,
final String otherGlMappings, final Boolean isDormancyTrackingActive, final Long daysToInactive,
final Long daysToDormancy, final Long daysToEscheat) {
this.name = name;
this.shortName = shortName;
this.description = description;
this.currency = currency;
this.nominalAnnualInterestRate = interestRate;
this.interestCompoundingPeriodType = interestCompoundingPeriodType.getValue();
this.interestPostingPeriodType = interestPostingPeriodType.getValue();
this.interestCalculationType = interestCalculationType.getValue();
this.interestCalculationDaysInYearType = interestCalculationDaysInYearType.getValue();
if (minRequiredOpeningBalance != null) {
this.minRequiredOpeningBalance = Money.of(currency, minRequiredOpeningBalance).getAmount();
}
this.lockinPeriodFrequency = lockinPeriodFrequency;
if (lockinPeriodFrequency != null && lockinPeriodFrequencyType != null) {
this.lockinPeriodFrequencyType = lockinPeriodFrequencyType.getValue();
}
this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer;
if (accountingRuleType != null) {
this.accountingRule = accountingRuleType.getValue();
}
if (charges != null) {
this.charges = charges;
}
validateLockinDetails();
this.allowOverdraft = allowOverdraft;
this.overdraftLimit = overdraftLimit;
this.nominalAnnualInterestRateOverdraft = nominalAnnualInterestRateOverdraft;
this.minOverdraftForInterestCalculation = minOverdraftForInterestCalculation;
ensureOverdraftLimitsSetForOverdraftAccounts();
this.enforceMinRequiredBalance = enforceMinRequiredBalance;
this.minRequiredBalance = minRequiredBalance;
this.lienAllowed = lienAllowed;
this.maxAllowedLienLimit = maxAllowedLienLimit;
this.minBalanceForInterestCalculation = minBalanceForInterestCalculation;
this.withHoldTax = withHoldTax;
this.taxGroup = taxGroup;
this.otherGlMappings = otherGlMappings;
this.isDormancyTrackingActive = isDormancyTrackingActive == null ? Boolean.FALSE : isDormancyTrackingActive;
this.daysToInactive = daysToInactive;
this.daysToDormancy = daysToDormancy;
this.daysToEscheat = daysToEscheat;
}
private void ensureOverdraftLimitsSetForOverdraftAccounts() {
if (this.allowOverdraft) {
this.overdraftLimit = this.overdraftLimit == null ? BigDecimal.ZERO : this.overdraftLimit;
this.nominalAnnualInterestRateOverdraft = this.nominalAnnualInterestRateOverdraft == null ? BigDecimal.ZERO
: this.nominalAnnualInterestRateOverdraft;
this.minOverdraftForInterestCalculation = this.minOverdraftForInterestCalculation == null ? BigDecimal.ZERO
: this.minOverdraftForInterestCalculation;
}
}
private void validateLockinDetails() {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource("currentproduct");
if (this.lockinPeriodFrequency == null) {
baseDataValidator.reset().parameter(lockinPeriodFrequencyTypeParamName).value(this.lockinPeriodFrequencyType)
.ignoreIfNull().inMinMaxRange(0, 3);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
public MonetaryCurrency currency() {
return this.currency.copy();
}
public BigDecimal nominalAnnualInterestRate() {
return this.nominalAnnualInterestRate;
}
public Integer getAccountingType() {
return this.accountingRule;
}
public boolean withHoldTax() {
return this.withHoldTax;
}
public TaxGroup getTaxGroup() {
return this.taxGroup;
}
public void setTaxGroup(final TaxGroup taxGroup) {
this.taxGroup = taxGroup;
}
public CurrentProductFacilityConfig getFacilityConfig() {
return this.facilityConfig;
}
public void setFacilityConfig(CurrentProductFacilityConfig facilityConfig) {
this.facilityConfig = facilityConfig;
}
public Set<CurrentProductFacilityLimit> getFacilityLimits() {
return this.facilityLimits;
}
public void setFacilityLimits(Set<CurrentProductFacilityLimit> facilityLimits) {
this.facilityLimits = facilityLimits;
}
public CurrentProductDepositLimitKyc getDepositLimitKyc() {
return this.depositLimitKyc;
}
public void setDepositLimitKyc(CurrentProductDepositLimitKyc depositLimitKyc) {
this.depositLimitKyc = depositLimitKyc;
}
public boolean update(final Set<Charge> newCharges) {
if (newCharges == null) {
return false;
}
final Set<Charge> currentSetOfCharges = this.charges == null ? new HashSet<>() : this.charges;
if (!currentSetOfCharges.equals(newCharges)) {
this.charges = newCharges;
return true;
}
return false;
}
public Map<String, Object> update(final JsonCommand command) {
final Map<String, Object> actualChanges = new LinkedHashMap<>(10);
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(nameParamName, this.name)) {
final String newValue = command.stringValueOfParameterNamed(nameParamName);
actualChanges.put(nameParamName, newValue);
this.name = newValue;
}
if (command.isChangeInStringParameterNamed(shortNameParamName, this.shortName)) {
final String newValue = command.stringValueOfParameterNamed(shortNameParamName);
actualChanges.put(shortNameParamName, newValue);
this.shortName = newValue;
}
if (command.isChangeInStringParameterNamed(descriptionParamName, this.description)) {
final String newValue = command.stringValueOfParameterNamed(descriptionParamName);
actualChanges.put(descriptionParamName, newValue);
this.description = newValue;
}
Integer digitsAfterDecimal = this.currency.getDigitsAfterDecimal();
if (command.isChangeInIntegerParameterNamed(digitsAfterDecimalParamName, digitsAfterDecimal)) {
final Integer newValue = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
actualChanges.put(digitsAfterDecimalParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
digitsAfterDecimal = newValue;
this.currency = new MonetaryCurrency(this.currency.getCode(), digitsAfterDecimal, this.currency.getInMultiplesOf());
}
String currencyCode = this.currency.getCode();
if (command.isChangeInStringParameterNamed(currencyCodeParamName, currencyCode)) {
final String newValue = command.stringValueOfParameterNamed(currencyCodeParamName);
actualChanges.put(currencyCodeParamName, newValue);
currencyCode = newValue;
this.currency = new MonetaryCurrency(currencyCode, this.currency.getDigitsAfterDecimal(), this.currency.getInMultiplesOf());
}
Integer inMultiplesOf = this.currency.getInMultiplesOf();
if (command.isChangeInIntegerParameterNamed(inMultiplesOfParamName, inMultiplesOf)) {
final Integer newValue = command.integerValueOfParameterNamed(inMultiplesOfParamName);
actualChanges.put(inMultiplesOfParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
inMultiplesOf = newValue;
this.currency = new MonetaryCurrency(this.currency.getCode(), this.currency.getDigitsAfterDecimal(), inMultiplesOf);
}
if (command.isChangeInBigDecimalParameterNamed(nominalAnnualInterestRateParamName, this.nominalAnnualInterestRate)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
actualChanges.put(nominalAnnualInterestRateParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.nominalAnnualInterestRate = newValue;
}
if (command.isChangeInIntegerParameterNamed(interestCompoundingPeriodTypeParamName, this.interestCompoundingPeriodType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
actualChanges.put(interestCompoundingPeriodTypeParamName, newValue);
this.interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(interestPostingPeriodTypeParamName, this.interestPostingPeriodType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
actualChanges.put(interestPostingPeriodTypeParamName, newValue);
this.interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(newValue).getValue();
}
if (command.isChangeInIntegerParameterNamed(interestCalculationTypeParamName, this.interestCalculationType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
actualChanges.put(interestCalculationTypeParamName, newValue);
this.interestCalculationType = newValue;
}
if (command.isChangeInIntegerParameterNamed(interestCalculationDaysInYearTypeParamName, this.interestCalculationDaysInYearType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
actualChanges.put(interestCalculationDaysInYearTypeParamName, newValue);
this.interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(newValue).getValue();
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredOpeningBalanceParamName, this.minRequiredOpeningBalance)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
actualChanges.put(minRequiredOpeningBalanceParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minRequiredOpeningBalance = newValue;
}
if (command.isChangeInIntegerParameterNamedDefaultingZeroToNull(lockinPeriodFrequencyParamName, this.lockinPeriodFrequency)) {
final Integer newValue = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
actualChanges.put(lockinPeriodFrequencyParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.lockinPeriodFrequency = newValue;
}
if (command.isChangeInIntegerParameterNamed(lockinPeriodFrequencyTypeParamName, this.lockinPeriodFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
actualChanges.put(lockinPeriodFrequencyTypeParamName, newValue);
this.lockinPeriodFrequencyType = newValue != null ? SavingsPeriodFrequencyType.fromInt(newValue).getValue() : newValue;
}
if (this.lockinPeriodFrequency == null) {
this.lockinPeriodFrequencyType = null;
}
if (command.isChangeInBooleanParameterNamed(withdrawalFeeForTransfersParamName, this.withdrawalFeeApplicableForTransfer)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
actualChanges.put(withdrawalFeeForTransfersParamName, newValue);
this.withdrawalFeeApplicableForTransfer = newValue;
}
if (command.isChangeInIntegerParameterNamed(accountingRuleParamName, this.accountingRule)) {
final Integer newValue = command.integerValueOfParameterNamed(accountingRuleParamName);
actualChanges.put(accountingRuleParamName, newValue);
this.accountingRule = newValue;
}
if (command.hasParameter(chargesParamName)) {
final JsonArray jsonArray = command.arrayOfParameterNamed(chargesParamName);
if (jsonArray != null) {
actualChanges.put(chargesParamName, command.jsonFragment(chargesParamName));
}
}
if (command.isChangeInBooleanParameterNamed(allowOverdraftParamName, this.allowOverdraft)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
actualChanges.put(allowOverdraftParamName, newValue);
this.allowOverdraft = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(overdraftLimitParamName, this.overdraftLimit)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(overdraftLimitParamName);
actualChanges.put(overdraftLimitParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.overdraftLimit = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(nominalAnnualInterestRateOverdraftParamName, this.nominalAnnualInterestRateOverdraft)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(nominalAnnualInterestRateOverdraftParamName);
actualChanges.put(nominalAnnualInterestRateOverdraftParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.nominalAnnualInterestRateOverdraft = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minOverdraftForInterestCalculationParamName, this.minOverdraftForInterestCalculation)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minOverdraftForInterestCalculationParamName);
actualChanges.put(minOverdraftForInterestCalculationParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minOverdraftForInterestCalculation = newValue;
}
if (!this.allowOverdraft) {
this.overdraftLimit = null;
this.nominalAnnualInterestRateOverdraft = null;
this.minOverdraftForInterestCalculation = null;
}
if (command.isChangeInBooleanParameterNamed(enforceMinRequiredBalanceParamName, this.enforceMinRequiredBalance)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
actualChanges.put(enforceMinRequiredBalanceParamName, newValue);
this.enforceMinRequiredBalance = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minRequiredBalanceParamName, this.minRequiredBalance)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredBalanceParamName);
actualChanges.put(minRequiredBalanceParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minRequiredBalance = newValue;
}
if (command.isChangeInBooleanParameterNamed(lienAllowedParamName, this.lienAllowed)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName);
actualChanges.put(lienAllowedParamName, newValue);
this.lienAllowed = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(maxAllowedLienLimitParamName, this.maxAllowedLienLimit)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName);
actualChanges.put(maxAllowedLienLimitParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.maxAllowedLienLimit = newValue;
}
if (command.isChangeInBigDecimalParameterNamedDefaultingZeroToNull(minBalanceForInterestCalculationParamName, this.minBalanceForInterestCalculation)) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
actualChanges.put(minBalanceForInterestCalculationParamName, newValue);
actualChanges.put(localeParamName, localeAsInput);
this.minBalanceForInterestCalculation = newValue;
}
if (command.isChangeInBooleanParameterNamed(withHoldTaxParamName, this.withHoldTax)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
actualChanges.put(withHoldTaxParamName, newValue);
this.withHoldTax = newValue;
}
if (this.withHoldTax) {
if (this.taxGroup == null || command.isChangeInLongParameterNamed(taxGroupIdParamName, this.taxGroup.getId())) {
final Long newValue = command.longValueOfParameterNamed(taxGroupIdParamName);
actualChanges.put(taxGroupIdParamName, newValue);
}
} else {
this.taxGroup = null;
}
if (command.hasParameter(otherGlMappingsParamName)) {
final String newValue = command.hasParameter(otherGlMappingsParamName) ? command.jsonFragment(otherGlMappingsParamName) : null;
actualChanges.put(otherGlMappingsParamName, newValue);
this.otherGlMappings = newValue;
}
if (command.isChangeInBooleanParameterNamed(isDormancyTrackingActiveParamName, this.isDormancyTrackingActive)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(isDormancyTrackingActiveParamName);
actualChanges.put(isDormancyTrackingActiveParamName, newValue);
this.isDormancyTrackingActive = newValue;
}
if (command.isChangeInLongParameterNamed(daysToInactiveParamName, this.daysToInactive)) {
final Long newValue = command.longValueOfParameterNamed(daysToInactiveParamName);
actualChanges.put(daysToInactiveParamName, newValue);
this.daysToInactive = newValue;
}
if (command.isChangeInLongParameterNamed(daysToDormancyParamName, this.daysToDormancy)) {
final Long newValue = command.longValueOfParameterNamed(daysToDormancyParamName);
actualChanges.put(daysToDormancyParamName, newValue);
this.daysToDormancy = newValue;
}
if (command.isChangeInLongParameterNamed(daysToEscheatParamName, this.daysToEscheat)) {
final Long newValue = command.longValueOfParameterNamed(daysToEscheatParamName);
actualChanges.put(daysToEscheatParamName, newValue);
this.daysToEscheat = newValue;
}
// Enhanced fields
if (command.isChangeInStringParameterNamed(accountTypeParamName, this.accountType)) {
final String newValue = command.stringValueOfParameterNamed(accountTypeParamName);
actualChanges.put(accountTypeParamName, newValue);
this.accountType = newValue;
}
if (command.isChangeInStringParameterNamed(customerSegmentParamName, this.customerSegment)) {
final String newValue = command.stringValueOfParameterNamed(customerSegmentParamName);
actualChanges.put(customerSegmentParamName, newValue);
this.customerSegment = newValue;
}
if (command.isChangeInStringParameterNamed(productCodeParamName, this.productCode)) {
final String newValue = command.stringValueOfParameterNamed(productCodeParamName);
actualChanges.put(productCodeParamName, newValue);
this.productCode = newValue;
}
if (command.isChangeInBooleanParameterNamed(introducerRequiredParamName, this.introducerRequired)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName);
actualChanges.put(introducerRequiredParamName, newValue);
this.introducerRequired = newValue;
}
if (command.isChangeInBooleanParameterNamed(shareLinkingEnabledParamName, this.shareLinkingEnabled)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(shareLinkingEnabledParamName);
actualChanges.put(shareLinkingEnabledParamName, newValue);
this.shareLinkingEnabled = newValue;
}
if (command.isChangeInBooleanParameterNamed(nominationFacilityParamName, this.nominationFacility)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(nominationFacilityParamName);
actualChanges.put(nominationFacilityParamName, newValue);
this.nominationFacility = newValue;
}
if (command.isChangeInBooleanParameterNamed(dormancyAlertParamName, this.dormancyAlert)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(dormancyAlertParamName);
actualChanges.put(dormancyAlertParamName, newValue);
this.dormancyAlert = newValue;
}
if (command.isChangeInBooleanParameterNamed(autoPostParamName, this.autoPost)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(autoPostParamName);
actualChanges.put(autoPostParamName, newValue);
this.autoPost = newValue;
}
if (command.isChangeInBooleanParameterNamed(enableLockinPeriodParamName, this.enableLockinPeriod)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName);
actualChanges.put(enableLockinPeriodParamName, newValue);
this.enableLockinPeriod = newValue;
}
// New currency-step fields
if (command.isChangeInBooleanParameterNamed(balanceSwapOptionParamName, this.balanceSwapOption)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(balanceSwapOptionParamName);
actualChanges.put(balanceSwapOptionParamName, newValue);
this.balanceSwapOption = newValue;
}
if (command.isChangeInBooleanParameterNamed(msmeParamName, this.msme)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(msmeParamName);
actualChanges.put(msmeParamName, newValue);
this.msme = newValue;
}
if (command.isChangeInBooleanParameterNamed(autoDebitFacilityParamName, this.autoDebitFacility)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(autoDebitFacilityParamName);
actualChanges.put(autoDebitFacilityParamName, newValue);
this.autoDebitFacility = newValue;
}
// New OD-section fields
if (command.isChangeInStringParameterNamed(avgBalanceCalcPeriodParamName, this.avgBalanceCalcPeriod)) {
final String newValue = command.stringValueOfParameterNamed(avgBalanceCalcPeriodParamName);
actualChanges.put(avgBalanceCalcPeriodParamName, newValue);
this.avgBalanceCalcPeriod = newValue;
}
if (command.isChangeInStringParameterNamed(validForPeriodParamName, this.validForPeriod)) {
final String newValue = command.stringValueOfParameterNamed(validForPeriodParamName);
actualChanges.put(validForPeriodParamName, newValue);
this.validForPeriod = newValue;
}
if (command.isChangeInBooleanParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName, this.odAccountClosedZeroBalanceInDayClosing)) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName);
actualChanges.put(odAccountClosedZeroBalanceInDayClosingParamName, newValue);
this.odAccountClosedZeroBalanceInDayClosing = newValue;
}
// Clear OD-specific fields when overdraft is disabled
if (!this.allowOverdraft) {
this.avgBalanceCalcPeriod = null;
this.validForPeriod = null;
this.odAccountClosedZeroBalanceInDayClosing = false;
}
return actualChanges;
}
// Dormancy/misc getters needed by validator
public Boolean isDormancyTrackingActive() { return this.isDormancyTrackingActive; }
public Long getDaysToInactive() { return this.daysToInactive; }
public Long getDaysToDormancy() { return this.daysToDormancy; }
public Long getDaysToEscheat() { return this.daysToEscheat; }
// Getters and setters for enhanced fields
public String getAccountType() { return accountType; }
public void setAccountType(String accountType) { this.accountType = accountType; }
public String getCustomerSegment() { return customerSegment; }
public void setCustomerSegment(String customerSegment) { this.customerSegment = customerSegment; }
public String getProductCode() { return productCode; }
public void setProductCode(String productCode) { this.productCode = productCode; }
public LocalDate getEffectiveDate() { return effectiveDate; }
public void setEffectiveDate(LocalDate effectiveDate) { this.effectiveDate = effectiveDate; }
public LocalDate getEffectiveTo() { return effectiveTo; }
public void setEffectiveTo(LocalDate effectiveTo) { this.effectiveTo = effectiveTo; }
public LocalDate getEndDate() { return endDate; }
public void setEndDate(LocalDate endDate) { this.endDate = endDate; }
public boolean isIntroducerRequired() { return introducerRequired; }
public void setIntroducerRequired(boolean introducerRequired) { this.introducerRequired = introducerRequired; }
public boolean isShareLinkingEnabled() { return shareLinkingEnabled; }
public void setShareLinkingEnabled(boolean shareLinkingEnabled) { this.shareLinkingEnabled = shareLinkingEnabled; }
public boolean isNominationFacility() { return nominationFacility; }
public void setNominationFacility(boolean nominationFacility) { this.nominationFacility = nominationFacility; }
public boolean isDormancyAlert() { return dormancyAlert; }
public void setDormancyAlert(boolean dormancyAlert) { this.dormancyAlert = dormancyAlert; }
public Integer getInterestCalculateSelector() { return interestCalculateSelector; }
public void setInterestCalculateSelector(Integer interestCalculateSelector) { this.interestCalculateSelector = interestCalculateSelector; }
public String getAveragePeriod() { return averagePeriod; }
public void setAveragePeriod(String averagePeriod) { this.averagePeriod = averagePeriod; }
public boolean isAutoPost() { return autoPost; }
public void setAutoPost(boolean autoPost) { this.autoPost = autoPost; }
public Integer getFrequencyFixDay() { return frequencyFixDay; }
public void setFrequencyFixDay(Integer frequencyFixDay) { this.frequencyFixDay = frequencyFixDay; }
public LocalDate getLastInterestDate() { return lastInterestDate; }
public void setLastInterestDate(LocalDate lastInterestDate) { this.lastInterestDate = lastInterestDate; }
public boolean isEnableLockinPeriod() { return enableLockinPeriod; }
public void setEnableLockinPeriod(boolean enableLockinPeriod) { this.enableLockinPeriod = enableLockinPeriod; }
public String getBalanceFrequency() { return balanceFrequency; }
public void setBalanceFrequency(String balanceFrequency) { this.balanceFrequency = balanceFrequency; }
public Boolean getAccountClosedZeroBalanceInDayClosing() { return accountClosedZeroBalanceInDayClosing; }
public void setAccountClosedZeroBalanceInDayClosing(Boolean val) { this.accountClosedZeroBalanceInDayClosing = val; }
// New field getters/setters
public boolean isBalanceSwapOption() { return balanceSwapOption; }
public void setBalanceSwapOption(boolean balanceSwapOption) { this.balanceSwapOption = balanceSwapOption; }
public boolean isMsme() { return msme; }
public void setMsme(boolean msme) { this.msme = msme; }
public boolean isAutoDebitFacility() { return autoDebitFacility; }
public void setAutoDebitFacility(boolean autoDebitFacility) { this.autoDebitFacility = autoDebitFacility; }
public String getAvgBalanceCalcPeriod() { return avgBalanceCalcPeriod; }
public void setAvgBalanceCalcPeriod(String avgBalanceCalcPeriod) { this.avgBalanceCalcPeriod = avgBalanceCalcPeriod; }
public String getValidForPeriod() { return validForPeriod; }
public void setValidForPeriod(String validForPeriod) { this.validForPeriod = validForPeriod; }
public boolean isOdAccountClosedZeroBalanceInDayClosing() { return odAccountClosedZeroBalanceInDayClosing; }
public void setOdAccountClosedZeroBalanceInDayClosing(boolean val) { this.odAccountClosedZeroBalanceInDayClosing = val; }
}
@@ -0,0 +1,429 @@
/**
* 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.currentproduct.domain;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityLimitsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoPostParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.averagePeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.currencyCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToDormancyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToEscheatParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.daysToInactiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.depositLimitKycParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.descriptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.digitsAfterDecimalParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.dormancyAlertParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.effectiveToParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enableLockinPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.balanceSwapOptionParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.msmeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.avgBalanceCalcPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.validForPeriodParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.odAccountClosedZeroBalanceInDayClosingParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.endDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.frequencyFixDayParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.idParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.inMultiplesOfParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculateSelectorParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationDaysInYearTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCalculationTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestCompoundingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.interestPostingPeriodTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerRequiredParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.isDormancyTrackingActiveParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lastInterestDateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lienAllowedParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.lockinPeriodFrequencyTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.maxAllowedLienLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minBalanceForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minOverdraftForInterestCalculationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minRequiredOpeningBalanceParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominalAnnualInterestRateParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.otherGlMappingsParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.overdraftLimitParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.productCodeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shareLinkingEnabledParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.shortNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
import org.apache.fineract.accounting.common.AccountingRuleType;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.exception.ChargeCannotBeAppliedToException;
import org.apache.fineract.portfolio.savings.SavingsCompoundingInterestPeriodType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationDaysInYearType;
import org.apache.fineract.portfolio.savings.SavingsInterestCalculationType;
import org.apache.fineract.portfolio.savings.SavingsPeriodFrequencyType;
import org.apache.fineract.portfolio.savings.SavingsPostingInterestPeriodType;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.apache.fineract.portfolio.tax.domain.TaxGroupRepositoryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CurrentProductAssembler {
private final ChargeRepositoryWrapper chargeRepository;
private final TaxGroupRepositoryWrapper taxGroupRepository;
@Autowired
public CurrentProductAssembler(final ChargeRepositoryWrapper chargeRepository,
final TaxGroupRepositoryWrapper taxGroupRepository) {
this.chargeRepository = chargeRepository;
this.taxGroupRepository = taxGroupRepository;
}
public CurrentProduct assemble(final JsonCommand command) {
final String name = command.stringValueOfParameterNamed(nameParamName);
final String shortName = command.stringValueOfParameterNamed(shortNameParamName);
final String description = command.stringValueOfParameterNamedAllowingNull(descriptionParamName);
final String currencyCode = command.stringValueOfParameterNamed(currencyCodeParamName);
final Integer digitsAfterDecimal = command.integerValueOfParameterNamed(digitsAfterDecimalParamName);
final Integer inMultiplesOf = command.integerValueOfParameterNamed(inMultiplesOfParamName);
final MonetaryCurrency currency = new MonetaryCurrency(currencyCode, digitsAfterDecimal, inMultiplesOf);
BigDecimal interestRate = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateParamName);
if (interestRate == null) {
interestRate = BigDecimal.ZERO;
}
SavingsCompoundingInterestPeriodType interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.DAILY;
final Integer interestPeriodTypeValue = command.integerValueOfParameterNamed(interestCompoundingPeriodTypeParamName);
if (interestPeriodTypeValue != null) {
interestCompoundingPeriodType = SavingsCompoundingInterestPeriodType.fromInt(interestPeriodTypeValue);
}
SavingsPostingInterestPeriodType interestPostingPeriodType = null;
final Integer interestPostingPeriodTypeValue = command.integerValueOfParameterNamed(interestPostingPeriodTypeParamName);
if (interestPostingPeriodTypeValue != null) {
interestPostingPeriodType = SavingsPostingInterestPeriodType.fromInt(interestPostingPeriodTypeValue);
}
SavingsInterestCalculationType interestCalculationType = SavingsInterestCalculationType.DAILY_BALANCE;
final Integer interestCalculationTypeValue = command.integerValueOfParameterNamed(interestCalculationTypeParamName);
if (interestCalculationTypeValue != null) {
interestCalculationType = SavingsInterestCalculationType.fromInt(interestCalculationTypeValue);
}
SavingsInterestCalculationDaysInYearType interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.DAYS_365;
final Integer interestCalculationDaysInYearTypeValue = command.integerValueOfParameterNamed(interestCalculationDaysInYearTypeParamName);
if (interestCalculationDaysInYearTypeValue != null) {
interestCalculationDaysInYearType = SavingsInterestCalculationDaysInYearType.fromInt(interestCalculationDaysInYearTypeValue);
}
final BigDecimal minRequiredOpeningBalance = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minRequiredOpeningBalanceParamName);
final Integer lockinPeriodFrequency = command.integerValueOfParameterNamedDefaultToNullIfZero(lockinPeriodFrequencyParamName);
SavingsPeriodFrequencyType lockinPeriodFrequencyType = null;
final Integer lockinPeriodFrequencyTypeValue = command.integerValueOfParameterNamed(lockinPeriodFrequencyTypeParamName);
if (lockinPeriodFrequencyTypeValue != null) {
lockinPeriodFrequencyType = SavingsPeriodFrequencyType.fromInt(lockinPeriodFrequencyTypeValue);
}
boolean isWithdrawalFeeApplicableForTransfer = false;
if (command.parameterExists(withdrawalFeeForTransfersParamName)) {
isWithdrawalFeeApplicableForTransfer = command.booleanPrimitiveValueOfParameterNamed(withdrawalFeeForTransfersParamName);
}
final AccountingRuleType accountingRuleType = AccountingRuleType.fromInt(command.integerValueOfParameterNamed("accountingRule"));
final Set<Charge> charges = assembleListOfCurrentProductCharges(command, currencyCode);
boolean allowOverdraft = false;
if (command.parameterExists(allowOverdraftParamName)) {
allowOverdraft = command.booleanPrimitiveValueOfParameterNamed(allowOverdraftParamName);
}
BigDecimal overdraftLimit = BigDecimal.ZERO;
if (command.parameterExists(overdraftLimitParamName)) {
overdraftLimit = command.bigDecimalValueOfParameterNamed(overdraftLimitParamName);
}
BigDecimal nominalAnnualInterestRateOverdraft = BigDecimal.ZERO;
if (command.parameterExists(nominalAnnualInterestRateOverdraftParamName)) {
nominalAnnualInterestRateOverdraft = command.bigDecimalValueOfParameterNamed(nominalAnnualInterestRateOverdraftParamName);
}
BigDecimal minOverdraftForInterestCalculation = BigDecimal.ZERO;
if (command.parameterExists(minOverdraftForInterestCalculationParamName)) {
minOverdraftForInterestCalculation = command.bigDecimalValueOfParameterNamed(minOverdraftForInterestCalculationParamName);
}
boolean enforceMinRequiredBalance = false;
if (command.parameterExists(enforceMinRequiredBalanceParamName)) {
enforceMinRequiredBalance = command.booleanPrimitiveValueOfParameterNamed(enforceMinRequiredBalanceParamName);
}
BigDecimal minRequiredBalance = BigDecimal.ZERO;
if (command.parameterExists(minRequiredBalanceParamName)) {
minRequiredBalance = command.bigDecimalValueOfParameterNamed(minRequiredBalanceParamName);
}
boolean lienAllowed = false;
if (command.parameterExists(lienAllowedParamName)) {
lienAllowed = command.booleanPrimitiveValueOfParameterNamed(lienAllowedParamName);
}
BigDecimal maxAllowedLienLimit = BigDecimal.ZERO;
if (command.parameterExists(maxAllowedLienLimitParamName)) {
maxAllowedLienLimit = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(maxAllowedLienLimitParamName);
}
final BigDecimal minBalanceForInterestCalculation = command.bigDecimalValueOfParameterNamedDefaultToNullIfZero(minBalanceForInterestCalculationParamName);
boolean withHoldTax = command.booleanPrimitiveValueOfParameterNamed(withHoldTaxParamName);
final TaxGroup taxGroup = assembleTaxGroup(command);
final Boolean isDormancyTrackingActive = command.booleanObjectValueOfParameterNamed(isDormancyTrackingActiveParamName);
final Long daysToInactive = command.longValueOfParameterNamed(daysToInactiveParamName);
final Long daysToDormancy = command.longValueOfParameterNamed(daysToDormancyParamName);
final Long daysToEscheat = command.longValueOfParameterNamed(daysToEscheatParamName);
final String otherGlMappings = command.hasParameter(otherGlMappingsParamName) ? command.jsonFragment(otherGlMappingsParamName) : null;
CurrentProduct product = CurrentProduct.createNew(name, shortName, description, currency, interestRate,
interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, isWithdrawalFeeApplicableForTransfer, accountingRuleType, charges,
allowOverdraft, overdraftLimit, enforceMinRequiredBalance, minRequiredBalance, lienAllowed,
maxAllowedLienLimit, minBalanceForInterestCalculation, nominalAnnualInterestRateOverdraft,
minOverdraftForInterestCalculation, withHoldTax, taxGroup, otherGlMappings,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
// Set enhanced fields
if (command.parameterExists(accountTypeParamName)) {
product.setAccountType(command.stringValueOfParameterNamed(accountTypeParamName));
}
if (command.parameterExists(customerSegmentParamName)) {
product.setCustomerSegment(command.stringValueOfParameterNamed(customerSegmentParamName));
}
if (command.parameterExists(productCodeParamName)) {
product.setProductCode(command.stringValueOfParameterNamed(productCodeParamName));
}
if (command.parameterExists(effectiveDateParamName)) {
product.setEffectiveDate(command.localDateValueOfParameterNamed(effectiveDateParamName));
}
if (command.parameterExists(effectiveToParamName)) {
product.setEffectiveTo(command.localDateValueOfParameterNamed(effectiveToParamName));
}
if (command.parameterExists(endDateParamName)) {
product.setEndDate(command.localDateValueOfParameterNamed(endDateParamName));
}
if (command.parameterExists(introducerRequiredParamName)) {
product.setIntroducerRequired(command.booleanPrimitiveValueOfParameterNamed(introducerRequiredParamName));
}
if (command.parameterExists(shareLinkingEnabledParamName)) {
product.setShareLinkingEnabled(command.booleanPrimitiveValueOfParameterNamed(shareLinkingEnabledParamName));
}
if (command.parameterExists(nominationFacilityParamName)) {
product.setNominationFacility(command.booleanPrimitiveValueOfParameterNamed(nominationFacilityParamName));
}
if (command.parameterExists(dormancyAlertParamName)) {
product.setDormancyAlert(command.booleanPrimitiveValueOfParameterNamed(dormancyAlertParamName));
}
if (command.parameterExists(interestCalculateSelectorParamName)) {
product.setInterestCalculateSelector(command.integerValueOfParameterNamed(interestCalculateSelectorParamName));
}
if (command.parameterExists(averagePeriodParamName)) {
product.setAveragePeriod(command.stringValueOfParameterNamed(averagePeriodParamName));
}
if (command.parameterExists(autoPostParamName)) {
product.setAutoPost(command.booleanPrimitiveValueOfParameterNamed(autoPostParamName));
}
if (command.parameterExists(frequencyFixDayParamName)) {
product.setFrequencyFixDay(command.integerValueOfParameterNamed(frequencyFixDayParamName));
}
if (command.parameterExists(lastInterestDateParamName)) {
product.setLastInterestDate(command.localDateValueOfParameterNamed(lastInterestDateParamName));
}
if (command.parameterExists(enableLockinPeriodParamName)) {
product.setEnableLockinPeriod(command.booleanPrimitiveValueOfParameterNamed(enableLockinPeriodParamName));
}
if (command.parameterExists("balanceFrequency")) {
product.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency"));
}
if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) {
product.setAccountClosedZeroBalanceInDayClosing(command.booleanObjectValueOfParameterNamed("accountClosedZeroBalanceInDayClosing"));
}
// New currency-step fields
if (command.parameterExists(balanceSwapOptionParamName)) {
product.setBalanceSwapOption(command.booleanPrimitiveValueOfParameterNamed(balanceSwapOptionParamName));
}
if (command.parameterExists(msmeParamName)) {
product.setMsme(command.booleanPrimitiveValueOfParameterNamed(msmeParamName));
}
if (command.parameterExists(autoDebitFacilityParamName)) {
product.setAutoDebitFacility(command.booleanPrimitiveValueOfParameterNamed(autoDebitFacilityParamName));
}
// New OD-section fields (only set when overdraft is allowed)
if (allowOverdraft) {
if (command.parameterExists(avgBalanceCalcPeriodParamName)) {
product.setAvgBalanceCalcPeriod(command.stringValueOfParameterNamed(avgBalanceCalcPeriodParamName));
}
if (command.parameterExists(validForPeriodParamName)) {
product.setValidForPeriod(command.stringValueOfParameterNamed(validForPeriodParamName));
}
if (command.parameterExists(odAccountClosedZeroBalanceInDayClosingParamName)) {
product.setOdAccountClosedZeroBalanceInDayClosing(command.booleanPrimitiveValueOfParameterNamed(odAccountClosedZeroBalanceInDayClosingParamName));
}
}
return product;
}
public Set<Charge> assembleListOfCurrentProductCharges(final JsonCommand command, final String currencyCode) {
final Set<Charge> charges = new HashSet<>();
if (command.parameterExists(chargesParamName)) {
final JsonArray chargesArray = command.arrayOfParameterNamed(chargesParamName);
if (chargesArray != null) {
for (int i = 0; i < chargesArray.size(); i++) {
final JsonObject jsonObject = chargesArray.get(i).getAsJsonObject();
if (jsonObject.has(idParamName)) {
final Long id = jsonObject.get(idParamName).getAsLong();
final Charge charge = this.chargeRepository.findOneWithNotFoundDetection(id);
if (!charge.isSavingsCharge()) {
final String errorMessage = "Charge with identifier " + charge.getId()
+ " cannot be applied to Current product.";
throw new ChargeCannotBeAppliedToException("current.product", errorMessage, charge.getId());
}
if (!currencyCode.equals(charge.getCurrencyCode())) {
final String errorMessage = "Charge and Current Product must have the same currency.";
throw new InvalidCurrencyException("charge", "attach.to.current.product", errorMessage);
}
charges.add(charge);
}
}
}
}
return charges;
}
public TaxGroup assembleTaxGroup(final JsonCommand command) {
final Long taxGroupId = command.longValueOfParameterNamed(taxGroupIdParamName);
TaxGroup taxGroup = null;
if (taxGroupId != null) {
taxGroup = this.taxGroupRepository.findOneWithNotFoundDetection(taxGroupId);
}
return taxGroup;
}
public CurrentProductFacilityConfig assembleAccountFacilityConfig(final JsonCommand command, final CurrentProduct product) {
if (!command.hasParameter(accountFacilityConfigParamName)) {
return null;
}
JsonElement facilityConfigElement = command.parsedJson().getAsJsonObject().get(accountFacilityConfigParamName);
if (facilityConfigElement == null || facilityConfigElement.isJsonNull()) {
return null;
}
final JsonObject facilityConfigJson = facilityConfigElement.getAsJsonObject();
CurrentProductFacilityConfig config = new CurrentProductFacilityConfig(product);
config.setAtmFacility(facilityConfigJson.has("atmFacility") ? facilityConfigJson.get("atmFacility").getAsBoolean() : false);
config.setAtmWithdrawalFacility(facilityConfigJson.has("atmWithdrawalFacility") ? facilityConfigJson.get("atmWithdrawalFacility").getAsBoolean() : false);
config.setUpiFacility(facilityConfigJson.has("upiFacility") ? facilityConfigJson.get("upiFacility").getAsBoolean() : false);
config.setPosTransactionsFacility(facilityConfigJson.has("posTransactionsFacility") ? facilityConfigJson.get("posTransactionsFacility").getAsBoolean() : false);
config.setChequeBook(facilityConfigJson.has("chequeBook") ? facilityConfigJson.get("chequeBook").getAsBoolean() : false);
config.setImpsFacility(facilityConfigJson.has("impsFacility") ? facilityConfigJson.get("impsFacility").getAsBoolean() : false);
config.setSmsAlerts(facilityConfigJson.has("smsAlerts") ? facilityConfigJson.get("smsAlerts").getAsBoolean() : false);
config.setBbpsFacility(facilityConfigJson.has("bbpsFacility") ? facilityConfigJson.get("bbpsFacility").getAsBoolean() : false);
config.setInsuranceCover(facilityConfigJson.has("insuranceCover") ? facilityConfigJson.get("insuranceCover").getAsBoolean() : false);
config.setBepgFacility(facilityConfigJson.has("bepgFacility") ? facilityConfigJson.get("bepgFacility").getAsBoolean() : false);
config.setNeftFacility(facilityConfigJson.has("neftFacility") ? facilityConfigJson.get("neftFacility").getAsBoolean() : false);
config.setRtgsFacility(facilityConfigJson.has("rtgsFacility") ? facilityConfigJson.get("rtgsFacility").getAsBoolean() : false);
config.setInterBankTotalCapFacility(facilityConfigJson.has("interBankTotalCapFacility") ? facilityConfigJson.get("interBankTotalCapFacility").getAsBoolean() : false);
config.setWithdrawalFacility(facilityConfigJson.has("withdrawalFacility") ? facilityConfigJson.get("withdrawalFacility").getAsBoolean() : false);
config.setDepositFacilityKyc(facilityConfigJson.has("depositFacilityKyc") ? facilityConfigJson.get("depositFacilityKyc").getAsBoolean() : false);
config.setVirtualDebitCard(facilityConfigJson.has("virtualDebitCard") ? facilityConfigJson.get("virtualDebitCard").getAsBoolean() : false);
return config;
}
public Set<CurrentProductFacilityLimit> assembleAccountFacilityLimits(final JsonCommand command, final CurrentProduct product) {
final Set<CurrentProductFacilityLimit> limits = new HashSet<>();
if (!command.hasParameter(accountFacilityLimitsParamName)) {
return limits;
}
final JsonArray limitsArray = command.arrayOfParameterNamed(accountFacilityLimitsParamName);
if (limitsArray == null) {
return limits;
}
for (int i = 0; i < limitsArray.size(); i++) {
final JsonObject limitJson = limitsArray.get(i).getAsJsonObject();
limits.add(CurrentProductFacilityLimit.create(product,
limitJson.has("limitType") ? limitJson.get("limitType").getAsString() : null,
limitJson.has("generalLimit") ? limitJson.get("generalLimit").getAsString() : null,
limitJson.has("minorAccountLimit") ? limitJson.get("minorAccountLimit").getAsString() : null,
limitJson.has("smallSavingLimit") ? limitJson.get("smallSavingLimit").getAsString() : null));
}
return limits;
}
public CurrentProductDepositLimitKyc assembleDepositLimitKyc(final JsonCommand command, final CurrentProduct product) {
if (!command.hasParameter(depositLimitKycParamName)) {
return null;
}
JsonElement kycElement = command.parsedJson().getAsJsonObject().get(depositLimitKycParamName);
if (kycElement == null || kycElement.isJsonNull()) {
return null;
}
final JsonObject kycJson = kycElement.getAsJsonObject();
CurrentProductDepositLimitKyc kyc = new CurrentProductDepositLimitKyc(product);
if (kycJson.has("general")) {
final JsonObject generalJson = kycJson.getAsJsonObject("general");
kyc.setGeneralKycType(generalJson.has("kycType") ? generalJson.get("kycType").getAsString() : null);
kyc.setGeneralLimit(generalJson.has("limit") ? generalJson.get("limit").getAsString() : null);
}
if (kycJson.has("minor")) {
final JsonObject minorJson = kycJson.getAsJsonObject("minor");
kyc.setMinorKycType(minorJson.has("kycType") ? minorJson.get("kycType").getAsString() : null);
kyc.setMinorLimit(minorJson.has("limit") ? minorJson.get("limit").getAsLong() : null);
}
if (kycJson.has("smallSaving")) {
final JsonObject smallSavingJson = kycJson.getAsJsonObject("smallSaving");
kyc.setSmallSavingKycType(smallSavingJson.has("kycType") ? smallSavingJson.get("kycType").getAsString() : null);
kyc.setSmallSavingLimit(smallSavingJson.has("limit") ? smallSavingJson.get("limit").getAsLong() : null);
}
return kyc;
}
}
@@ -0,0 +1,92 @@
/**
* 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.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_deposit_limit_kyc")
public class CurrentProductDepositLimitKyc extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@Column(name = "general_kyc_type", length = 50)
private String generalKycType;
@Column(name = "general_limit", length = 50)
private String generalLimit;
@Column(name = "minor_kyc_type", length = 50)
private String minorKycType;
@Column(name = "minor_limit")
private Long minorLimit;
@Column(name = "small_saving_kyc_type", length = 50)
private String smallSavingKycType;
@Column(name = "small_saving_limit")
private Long smallSavingLimit;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductDepositLimitKyc() {
this.createdDate = LocalDateTime.now();
}
public CurrentProductDepositLimitKyc(CurrentProduct currentProduct) {
this();
this.currentProduct = currentProduct;
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public String getGeneralKycType() { return generalKycType; }
public void setGeneralKycType(String generalKycType) { this.generalKycType = generalKycType; }
public String getGeneralLimit() { return generalLimit; }
public void setGeneralLimit(String generalLimit) { this.generalLimit = generalLimit; }
public String getMinorKycType() { return minorKycType; }
public void setMinorKycType(String minorKycType) { this.minorKycType = minorKycType; }
public Long getMinorLimit() { return minorLimit; }
public void setMinorLimit(Long minorLimit) { this.minorLimit = minorLimit; }
public String getSmallSavingKycType() { return smallSavingKycType; }
public void setSmallSavingKycType(String smallSavingKycType) { this.smallSavingKycType = smallSavingKycType; }
public Long getSmallSavingLimit() { return smallSavingLimit; }
public void setSmallSavingLimit(Long smallSavingLimit) { this.smallSavingLimit = smallSavingLimit; }
public LocalDateTime getCreatedDate() { return createdDate; }
public void updateFrom(CurrentProductDepositLimitKyc other) {
this.generalKycType = other.getGeneralKycType();
this.generalLimit = other.getGeneralLimit();
this.minorKycType = other.getMinorKycType();
this.minorLimit = other.getMinorLimit();
this.smallSavingKycType = other.getSmallSavingKycType();
this.smallSavingLimit = other.getSmallSavingLimit();
}
}
@@ -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.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentProductDepositLimitKycRepository extends JpaRepository<CurrentProductDepositLimitKyc, Long> {
CurrentProductDepositLimitKyc findByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,153 @@
/**
* 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.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_facility_config")
public class CurrentProductFacilityConfig extends AbstractPersistableCustom<Long> {
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@Column(name = "atm_facility")
private Boolean atmFacility = false;
@Column(name = "atm_withdrawal_facility")
private Boolean atmWithdrawalFacility = false;
@Column(name = "upi_facility")
private Boolean upiFacility = false;
@Column(name = "pos_transactions_facility")
private Boolean posTransactionsFacility = false;
@Column(name = "cheque_book")
private Boolean chequeBook = false;
@Column(name = "imps_facility")
private Boolean impsFacility = false;
@Column(name = "sms_alerts")
private Boolean smsAlerts = false;
@Column(name = "bbps_facility")
private Boolean bbpsFacility = false;
@Column(name = "insurance_cover")
private Boolean insuranceCover = false;
@Column(name = "bepg_facility")
private Boolean bepgFacility = false;
@Column(name = "neft_facility")
private Boolean neftFacility = false;
@Column(name = "rtgs_facility")
private Boolean rtgsFacility = false;
@Column(name = "inter_bank_total_cap_facility")
private Boolean interBankTotalCapFacility = false;
@Column(name = "withdrawal_facility")
private Boolean withdrawalFacility = false;
@Column(name = "deposit_facility_kyc")
private Boolean depositFacilityKyc = false;
@Column(name = "virtual_debit_card")
private Boolean virtualDebitCard = false;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductFacilityConfig() {
this.createdDate = LocalDateTime.now();
}
public CurrentProductFacilityConfig(CurrentProduct currentProduct) {
this();
this.currentProduct = currentProduct;
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public Boolean getAtmFacility() { return atmFacility; }
public void setAtmFacility(Boolean v) { this.atmFacility = v != null ? v : false; }
public Boolean getAtmWithdrawalFacility() { return atmWithdrawalFacility; }
public void setAtmWithdrawalFacility(Boolean v) { this.atmWithdrawalFacility = v != null ? v : false; }
public Boolean getUpiFacility() { return upiFacility; }
public void setUpiFacility(Boolean v) { this.upiFacility = v != null ? v : false; }
public Boolean getPosTransactionsFacility() { return posTransactionsFacility; }
public void setPosTransactionsFacility(Boolean v) { this.posTransactionsFacility = v != null ? v : false; }
public Boolean getChequeBook() { return chequeBook; }
public void setChequeBook(Boolean v) { this.chequeBook = v != null ? v : false; }
public Boolean getImpsFacility() { return impsFacility; }
public void setImpsFacility(Boolean v) { this.impsFacility = v != null ? v : false; }
public Boolean getSmsAlerts() { return smsAlerts; }
public void setSmsAlerts(Boolean v) { this.smsAlerts = v != null ? v : false; }
public Boolean getBbpsFacility() { return bbpsFacility; }
public void setBbpsFacility(Boolean v) { this.bbpsFacility = v != null ? v : false; }
public Boolean getInsuranceCover() { return insuranceCover; }
public void setInsuranceCover(Boolean v) { this.insuranceCover = v != null ? v : false; }
public Boolean getBepgFacility() { return bepgFacility; }
public void setBepgFacility(Boolean v) { this.bepgFacility = v != null ? v : false; }
public Boolean getNeftFacility() { return neftFacility; }
public void setNeftFacility(Boolean v) { this.neftFacility = v != null ? v : false; }
public Boolean getRtgsFacility() { return rtgsFacility; }
public void setRtgsFacility(Boolean v) { this.rtgsFacility = v != null ? v : false; }
public Boolean getInterBankTotalCapFacility() { return interBankTotalCapFacility; }
public void setInterBankTotalCapFacility(Boolean v) { this.interBankTotalCapFacility = v != null ? v : false; }
public Boolean getWithdrawalFacility() { return withdrawalFacility; }
public void setWithdrawalFacility(Boolean v) { this.withdrawalFacility = v != null ? v : false; }
public Boolean getDepositFacilityKyc() { return depositFacilityKyc; }
public void setDepositFacilityKyc(Boolean v) { this.depositFacilityKyc = v != null ? v : false; }
public Boolean getVirtualDebitCard() { return virtualDebitCard; }
public void setVirtualDebitCard(Boolean v) { this.virtualDebitCard = v != null ? v : false; }
public LocalDateTime getCreatedDate() { return createdDate; }
public void updateFrom(CurrentProductFacilityConfig other) {
this.atmFacility = other.getAtmFacility();
this.atmWithdrawalFacility = other.getAtmWithdrawalFacility();
this.upiFacility = other.getUpiFacility();
this.posTransactionsFacility = other.getPosTransactionsFacility();
this.chequeBook = other.getChequeBook();
this.impsFacility = other.getImpsFacility();
this.smsAlerts = other.getSmsAlerts();
this.bbpsFacility = other.getBbpsFacility();
this.insuranceCover = other.getInsuranceCover();
this.bepgFacility = other.getBepgFacility();
this.neftFacility = other.getNeftFacility();
this.rtgsFacility = other.getRtgsFacility();
this.interBankTotalCapFacility = other.getInterBankTotalCapFacility();
this.withdrawalFacility = other.getWithdrawalFacility();
this.depositFacilityKyc = other.getDepositFacilityKyc();
this.virtualDebitCard = other.getVirtualDebitCard();
}
}
@@ -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.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
public interface CurrentProductFacilityConfigRepository extends JpaRepository<CurrentProductFacilityConfig, Long> {
CurrentProductFacilityConfig findByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,83 @@
/**
* 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.currentproduct.domain;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
@Entity
@Table(name = "m_current_product_facility_limits")
public class CurrentProductFacilityLimit extends AbstractPersistableCustom<Long> {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "current_product_id", nullable = false)
private CurrentProduct currentProduct;
@Column(name = "limit_type", length = 100, nullable = false)
private String limitType;
@Column(name = "general_limit", length = 50)
private String generalLimit;
@Column(name = "minor_account_limit", length = 50)
private String minorAccountLimit;
@Column(name = "small_saving_limit", length = 50)
private String smallSavingLimit;
@Column(name = "created_date", nullable = false)
private LocalDateTime createdDate;
protected CurrentProductFacilityLimit() {
this.createdDate = LocalDateTime.now();
}
private CurrentProductFacilityLimit(CurrentProduct currentProduct, String limitType,
String generalLimit, String minorAccountLimit, String smallSavingLimit) {
this();
this.currentProduct = currentProduct;
this.limitType = limitType;
this.generalLimit = generalLimit;
this.minorAccountLimit = minorAccountLimit;
this.smallSavingLimit = smallSavingLimit;
}
public static CurrentProductFacilityLimit create(CurrentProduct currentProduct, String limitType,
String generalLimit, String minorAccountLimit, String smallSavingLimit) {
return new CurrentProductFacilityLimit(currentProduct, limitType, generalLimit, minorAccountLimit, smallSavingLimit);
}
public CurrentProduct getCurrentProduct() { return currentProduct; }
public void setCurrentProduct(CurrentProduct currentProduct) { this.currentProduct = currentProduct; }
public String getLimitType() { return limitType; }
public void setLimitType(String limitType) { this.limitType = limitType; }
public String getGeneralLimit() { return generalLimit; }
public void setGeneralLimit(String generalLimit) { this.generalLimit = generalLimit; }
public String getMinorAccountLimit() { return minorAccountLimit; }
public void setMinorAccountLimit(String minorAccountLimit) { this.minorAccountLimit = minorAccountLimit; }
public String getSmallSavingLimit() { return smallSavingLimit; }
public void setSmallSavingLimit(String smallSavingLimit) { this.smallSavingLimit = smallSavingLimit; }
public LocalDateTime getCreatedDate() { return createdDate; }
}
@@ -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.portfolio.currentproduct.domain;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.transaction.annotation.Transactional;
public interface CurrentProductFacilityLimitRepository extends JpaRepository<CurrentProductFacilityLimit, Long> {
List<CurrentProductFacilityLimit> findByCurrentProductId(Long currentProductId);
@Transactional
void deleteByCurrentProductId(Long currentProductId);
}
@@ -0,0 +1,25 @@
/**
* 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.currentproduct.domain;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface CurrentProductRepository extends JpaRepository<CurrentProduct, Long>, JpaSpecificationExecutor<CurrentProduct> {
}
@@ -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.portfolio.currentproduct.service;
import java.util.Collection;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
public interface CurrentProductReadPlatformService {
Collection<SavingsProductData> retrieveAll();
Collection<SavingsProductData> retrieveAllForLookup();
SavingsProductData retrieveOne(Long productId);
}
@@ -0,0 +1,346 @@
/**
* 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.currentproduct.service;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKyc;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfig;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.savings.service.SavingsEnumerations;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
@RequiredArgsConstructor
public class CurrentProductReadPlatformServiceImpl implements CurrentProductReadPlatformService {
private final PlatformSecurityContext context;
private final JdbcTemplate jdbcTemplate;
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
private final CurrentProductMapper currentProductRowMapper = new CurrentProductMapper();
private final CurrentProductLookupMapper currentProductLookupsRowMapper = new CurrentProductLookupMapper();
private static final Gson GSON = new Gson();
private static final Type OTHER_GL_MAPPINGS_TYPE = new TypeToken<Map<String, List<Map<String, Object>>>>() {}.getType();
@Override
public Collection<SavingsProductData> retrieveAll() {
this.context.authenticatedUser();
final String sql = "select " + this.currentProductRowMapper.schema();
return this.jdbcTemplate.query(sql, this.currentProductRowMapper); // NOSONAR
}
@Override
public Collection<SavingsProductData> retrieveAllForLookup() {
final String sql = "select " + this.currentProductLookupsRowMapper.schema();
return this.jdbcTemplate.query(sql, this.currentProductLookupsRowMapper); // NOSONAR
}
@Override
public SavingsProductData retrieveOne(final Long productId) {
try {
this.context.authenticatedUser();
final String sql = "select " + this.currentProductRowMapper.schema() + " where cp.id = ?";
SavingsProductData product = this.jdbcTemplate.queryForObject(sql, this.currentProductRowMapper, // NOSONAR
new Object[] { productId });
if (product != null) {
// Fetch Facility Config
CurrentProductFacilityConfig facilityConfig = facilityConfigRepository.findByCurrentProductId(productId);
if (facilityConfig != null) {
Map<String, Object> facilityConfigMap = new HashMap<>();
facilityConfigMap.put("atmFacility", facilityConfig.getAtmFacility());
facilityConfigMap.put("atmWithdrawalFacility", facilityConfig.getAtmWithdrawalFacility());
facilityConfigMap.put("upiFacility", facilityConfig.getUpiFacility());
facilityConfigMap.put("posTransactionsFacility", facilityConfig.getPosTransactionsFacility());
facilityConfigMap.put("chequeBook", facilityConfig.getChequeBook());
facilityConfigMap.put("impsFacility", facilityConfig.getImpsFacility());
facilityConfigMap.put("smsAlerts", facilityConfig.getSmsAlerts());
facilityConfigMap.put("bbpsFacility", facilityConfig.getBbpsFacility());
facilityConfigMap.put("insuranceCover", facilityConfig.getInsuranceCover());
facilityConfigMap.put("bepgFacility", facilityConfig.getBepgFacility());
facilityConfigMap.put("neftFacility", facilityConfig.getNeftFacility());
facilityConfigMap.put("rtgsFacility", facilityConfig.getRtgsFacility());
facilityConfigMap.put("interBankTotalCapFacility", facilityConfig.getInterBankTotalCapFacility());
facilityConfigMap.put("withdrawalFacility", facilityConfig.getWithdrawalFacility());
facilityConfigMap.put("depositFacilityKyc", facilityConfig.getDepositFacilityKyc());
facilityConfigMap.put("virtualDebitCard", facilityConfig.getVirtualDebitCard());
product.setAccountFacilityConfig(facilityConfigMap);
}
// Fetch Facility Limits
List<CurrentProductFacilityLimit> facilityLimits = facilityLimitRepository.findByCurrentProductId(productId);
if (facilityLimits != null && !facilityLimits.isEmpty()) {
List<Map<String, Object>> limits = facilityLimits.stream().map(limit -> {
Map<String, Object> limitMap = new HashMap<>();
limitMap.put("limitType", limit.getLimitType());
limitMap.put("generalLimit", limit.getGeneralLimit());
limitMap.put("minorAccountLimit", limit.getMinorAccountLimit());
limitMap.put("smallSavingLimit", limit.getSmallSavingLimit());
return limitMap;
}).toList();
product.setAccountFacilityLimits(limits);
}
// Fetch Deposit Limit KYC
CurrentProductDepositLimitKyc depositLimitKyc = depositLimitKycRepository.findByCurrentProductId(productId);
if (depositLimitKyc != null) {
Map<String, Object> depositLimitKycMap = new HashMap<>();
depositLimitKycMap.put("general", Map.of(
"kycType", depositLimitKyc.getGeneralKycType() != null ? depositLimitKyc.getGeneralKycType() : "",
"limit", depositLimitKyc.getGeneralLimit() != null ? depositLimitKyc.getGeneralLimit() : ""));
depositLimitKycMap.put("minor", Map.of(
"kycType", depositLimitKyc.getMinorKycType() != null ? depositLimitKyc.getMinorKycType() : "",
"limit", depositLimitKyc.getMinorLimit() != null ? depositLimitKyc.getMinorLimit() : 0L));
depositLimitKycMap.put("smallSaving", Map.of(
"kycType", depositLimitKyc.getSmallSavingKycType() != null ? depositLimitKyc.getSmallSavingKycType() : "",
"limit", depositLimitKyc.getSmallSavingLimit() != null ? depositLimitKyc.getSmallSavingLimit() : 0L));
product.setDepositLimitKyc(depositLimitKycMap);
}
}
return product;
} catch (final EmptyResultDataAccessException e) {
throw new SavingsProductNotFoundException(productId, e);
}
}
private static final class CurrentProductMapper implements RowMapper<SavingsProductData> {
private final String schemaSql;
CurrentProductMapper() {
final StringBuilder sqlBuilder = new StringBuilder(1000);
sqlBuilder.append("cp.id as id, cp.name as name, cp.short_name as shortName, cp.description as description, ");
sqlBuilder.append(
"cp.currency_code as currencyCode, cp.currency_digits as currencyDigits, cp.currency_multiplesof as inMultiplesOf, ");
sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, ");
sqlBuilder.append("cp.nominal_annual_interest_rate as nominalAnnualInterestRate, ");
sqlBuilder.append("cp.interest_compounding_period_enum as compoundingInterestPeriodType, ");
sqlBuilder.append("cp.interest_posting_period_enum as interestPostingPeriodType, ");
sqlBuilder.append("cp.interest_calculation_type_enum as interestCalculationType, ");
sqlBuilder.append("cp.interest_calculation_days_in_year_type_enum as interestCalculationDaysInYearType, ");
sqlBuilder.append("cp.min_required_opening_balance as minRequiredOpeningBalance, ");
sqlBuilder.append("cp.lockin_period_frequency as lockinPeriodFrequency,");
sqlBuilder.append("cp.lockin_period_frequency_enum as lockinPeriodFrequencyType, ");
sqlBuilder.append("cp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers, ");
sqlBuilder.append("cp.allow_overdraft as allowOverdraft, ");
sqlBuilder.append("cp.overdraft_limit as overdraftLimit, ");
sqlBuilder.append("cp.nominal_annual_interest_rate_overdraft as nominalAnnualInterestRateOverdraft, ");
sqlBuilder.append("cp.min_overdraft_for_interest_calculation as minOverdraftForInterestCalculation, ");
sqlBuilder.append("cp.min_required_balance as minRequiredBalance, ");
sqlBuilder.append("cp.enforce_min_required_balance as enforceMinRequiredBalance, ");
sqlBuilder.append("cp.max_allowed_lien_limit as maxAllowedLienLimit, ");
sqlBuilder.append("cp.is_lien_allowed as lienAllowed, ");
sqlBuilder.append("cp.min_balance_for_interest_calculation as minBalanceForInterestCalculation,");
sqlBuilder.append("cp.accounting_type as accountingType, ");
sqlBuilder.append("cp.withhold_tax as withHoldTax,");
sqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName, ");
sqlBuilder.append("cp.is_dormancy_tracking_active as isDormancyTrackingActive,");
sqlBuilder.append("cp.days_to_inactive as daysToInactive,");
sqlBuilder.append("cp.days_to_dormancy as daysToDormancy,");
sqlBuilder.append("cp.days_to_escheat as daysToEscheat, ");
sqlBuilder.append("cp.other_gl_mappings as otherGlMappings, ");
// Enhanced fields
sqlBuilder.append("cp.account_type as accountType, cp.customer_segment as customerSegment, cp.product_code as productCode, ");
sqlBuilder.append("cp.effective_date as effectiveDate, cp.effective_to as effectiveTo, cp.end_date as endDate, ");
sqlBuilder.append("cp.introducer_required as introducerRequired, cp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("cp.nomination_facility as nominationFacility, cp.dormancy_alert as dormancyAlert, ");
sqlBuilder.append("cp.interest_calculate_selector as interestCalculateSelector, cp.average_period as averagePeriod, ");
sqlBuilder.append("cp.auto_post as autoPost, cp.frequency_fix_day as frequencyFixDay, cp.last_interest_date as lastInterestDate, ");
sqlBuilder.append("cp.enable_lockin_period as enableLockinPeriod, ");
sqlBuilder.append("cp.balance_frequency as balanceFrequency, ");
sqlBuilder.append("cp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
sqlBuilder.append("cp.balance_swap_option as balanceSwapOption, cp.msme as msme, cp.auto_debit_facility as autoDebitFacility, ");
sqlBuilder.append("cp.avg_balance_calc_period as avgBalanceCalcPeriod, cp.valid_for_period as validForPeriod, ");
sqlBuilder.append("cp.od_account_closed_zero_balance_in_day_closing as odAccountClosedZeroBalanceInDayClosing ");
sqlBuilder.append("from m_current_product cp ");
sqlBuilder.append("join m_currency curr on curr.code = cp.currency_code ");
sqlBuilder.append("left join m_tax_group tg on tg.id = cp.tax_group_id ");
this.schemaSql = sqlBuilder.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String name = rs.getString("name");
final String shortName = rs.getString("shortName");
final String description = rs.getString("description");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, currencyDisplaySymbol,
currencyNameCode);
final BigDecimal nominalAnnualInterestRate = rs.getBigDecimal("nominalAnnualInterestRate");
final Integer compoundingInterestPeriodTypeValue = JdbcSupport.getInteger(rs, "compoundingInterestPeriodType");
final EnumOptionData compoundingInterestPeriodType = SavingsEnumerations
.compoundingInterestPeriodType(compoundingInterestPeriodTypeValue);
final Integer interestPostingPeriodTypeValue = JdbcSupport.getInteger(rs, "interestPostingPeriodType");
final EnumOptionData interestPostingPeriodType = SavingsEnumerations.interestPostingPeriodType(interestPostingPeriodTypeValue);
final Integer interestCalculationTypeValue = JdbcSupport.getInteger(rs, "interestCalculationType");
final EnumOptionData interestCalculationType = SavingsEnumerations.interestCalculationType(interestCalculationTypeValue);
EnumOptionData interestCalculationDaysInYearType = null;
final Integer interestCalculationDaysInYearTypeValue = JdbcSupport.getInteger(rs, "interestCalculationDaysInYearType");
if (interestCalculationDaysInYearTypeValue != null) {
interestCalculationDaysInYearType = SavingsEnumerations
.interestCalculationDaysInYearType(interestCalculationDaysInYearTypeValue);
}
final Integer accountingRuleId = JdbcSupport.getInteger(rs, "accountingType");
final EnumOptionData accountingRuleType = AccountingEnumerations.accountingRuleType(accountingRuleId);
final BigDecimal minRequiredOpeningBalance = rs.getBigDecimal("minRequiredOpeningBalance");
final Integer lockinPeriodFrequency = JdbcSupport.getInteger(rs, "lockinPeriodFrequency");
EnumOptionData lockinPeriodFrequencyType = null;
final Integer lockinPeriodFrequencyTypeValue = JdbcSupport.getInteger(rs, "lockinPeriodFrequencyType");
if (lockinPeriodFrequencyTypeValue != null) {
lockinPeriodFrequencyType = SavingsEnumerations.lockinPeriodFrequencyType(lockinPeriodFrequencyTypeValue);
}
final boolean withdrawalFeeForTransfers = rs.getBoolean("withdrawalFeeForTransfers");
final boolean allowOverdraft = rs.getBoolean("allowOverdraft");
final BigDecimal overdraftLimit = rs.getBigDecimal("overdraftLimit");
final BigDecimal nominalAnnualInterestRateOverdraft = rs.getBigDecimal("nominalAnnualInterestRateOverdraft");
final BigDecimal minOverdraftForInterestCalculation = rs.getBigDecimal("minOverdraftForInterestCalculation");
final BigDecimal minRequiredBalance = rs.getBigDecimal("minRequiredBalance");
final boolean enforceMinRequiredBalance = rs.getBoolean("enforceMinRequiredBalance");
final BigDecimal maxAllowedLienLimit = rs.getBigDecimal("maxAllowedLienLimit");
final boolean lienAllowed = rs.getBoolean("lienAllowed");
final BigDecimal minBalanceForInterestCalculation = rs.getBigDecimal("minBalanceForInterestCalculation");
final boolean withHoldTax = rs.getBoolean("withHoldTax");
final Long taxGroupId = JdbcSupport.getLong(rs, "taxGroupId");
final String taxGroupName = rs.getString("taxGroupName");
TaxGroupData taxGroupData = null;
if (taxGroupId != null) {
taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
}
final Boolean isDormancyTrackingActive = rs.getBoolean("isDormancyTrackingActive");
final Long daysToInactive = JdbcSupport.getLong(rs, "daysToInactive");
final Long daysToDormancy = JdbcSupport.getLong(rs, "daysToDormancy");
final Long daysToEscheat = JdbcSupport.getLong(rs, "daysToEscheat");
final Map<String, List<Map<String, Object>>> otherGlMappings = parseOtherGlMappings(rs.getString("otherGlMappings"));
SavingsProductData productData = SavingsProductData.instance(id, name, shortName, description, currency,
nominalAnnualInterestRate, compoundingInterestPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeForTransfers, accountingRuleType, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData, null, null, null,
isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, null);
productData.setOtherGlMappingsMap(otherGlMappings);
// Populate Enhanced Fields
productData.setAccountType(rs.getString("accountType"));
productData.setCustomerSegment(rs.getString("customerSegment"));
productData.setProductCode(rs.getString("productCode"));
productData.setEffectiveDate(JdbcSupport.getLocalDate(rs, "effectiveDate"));
productData.setEffectiveTo(JdbcSupport.getLocalDate(rs, "effectiveTo"));
productData.setEndDate(JdbcSupport.getLocalDate(rs, "endDate"));
productData.setIntroducerRequired(rs.getBoolean("introducerRequired"));
productData.setShareLinkingEnabled(rs.getBoolean("shareLinkingEnabled"));
productData.setNominationFacility(rs.getBoolean("nominationFacility"));
productData.setDormancyAlert(rs.getBoolean("dormancyAlert"));
productData.setInterestCalculateSelector(JdbcSupport.getInteger(rs, "interestCalculateSelector"));
productData.setAveragePeriod(rs.getString("averagePeriod"));
productData.setAutoPost(rs.getBoolean("autoPost"));
productData.setFrequencyFixDay(JdbcSupport.getInteger(rs, "frequencyFixDay"));
productData.setLastInterestDate(JdbcSupport.getLocalDate(rs, "lastInterestDate"));
productData.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
productData.setBalanceFrequency(rs.getString("balanceFrequency"));
productData.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
productData.setBalanceSwapOption(rs.getBoolean("balanceSwapOption"));
productData.setMsme(rs.getBoolean("msme"));
productData.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
productData.setAvgBalanceCalcPeriod(rs.getString("avgBalanceCalcPeriod"));
productData.setValidForPeriod(rs.getString("validForPeriod"));
productData.setOdAccountClosedZeroBalanceInDayClosing(rs.getBoolean("odAccountClosedZeroBalanceInDayClosing"));
return productData;
}
}
private static final class CurrentProductLookupMapper implements RowMapper<SavingsProductData> {
public String schema() {
return " cp.id as id, cp.name as name from m_current_product cp";
}
@Override
public SavingsProductData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final String name = rs.getString("name");
return SavingsProductData.lookup(id, name);
}
}
private static Map<String, List<Map<String, Object>>> parseOtherGlMappings(final String otherGlMappingsJson) {
if (otherGlMappingsJson == null || otherGlMappingsJson.isBlank()) {
return null;
}
return GSON.fromJson(otherGlMappingsJson, OTHER_GL_MAPPINGS_TYPE);
}
}
@@ -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.portfolio.currentproduct.service;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
public interface CurrentProductWritePlatformService {
CommandProcessingResult create(JsonCommand command);
CommandProcessingResult update(Long productId, JsonCommand command);
CommandProcessingResult delete(Long productId);
}
@@ -0,0 +1,267 @@
/**
* 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.currentproduct.service;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountingRuleParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.taxGroupIdParamName;
import jakarta.persistence.PersistenceException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKyc;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfig;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimit;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@RequiredArgsConstructor
public class CurrentProductWritePlatformServiceJpaRepositoryImpl implements CurrentProductWritePlatformService {
private final PlatformSecurityContext context;
private final CurrentProductRepository currentProductRepository;
private final CurrentProductDataValidator fromApiJsonDataValidator;
private final CurrentProductAssembler currentProductAssembler;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
private final CurrentProductFacilityConfigRepository facilityConfigRepository;
private final CurrentProductFacilityLimitRepository facilityLimitRepository;
private final CurrentProductDepositLimitKycRepository depositLimitKycRepository;
private void handleDataIntegrityIssues(final JsonCommand command, final Throwable realCause, final Exception dae) {
String msgCode = "error.msg." + DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME;
String msg = "Unknown data integrity issue with current product.";
String param = null;
Object[] msgArgs;
Throwable checkEx = realCause == null ? dae : realCause;
if (checkEx.getMessage().contains("cp_unq_name")) {
final String name = command.stringValueOfParameterNamed("name");
msgCode += ".duplicate.name";
msg = "Current product with name `" + name + "` already exists";
param = "name";
msgArgs = new Object[] { name, dae };
} else if (checkEx.getMessage().contains("cp_unq_short_name")) {
final String shortName = command.stringValueOfParameterNamed("shortName");
msgCode += ".duplicate.short.name";
msg = "Current product with short name `" + shortName + "` already exists";
param = "shortName";
msgArgs = new Object[] { shortName, dae };
} else if (checkEx.getMessage().contains("cp_unq_product_code")) {
final String productCode = command.stringValueOfParameterNamed("productCode");
msgCode += ".duplicate.product.code";
msg = "Current product with product code `" + productCode + "` already exists";
param = "productCode";
msgArgs = new Object[] { productCode, dae };
} else {
msgCode += ".unknown.data.integrity.issue";
msg = "Unknown data integrity issue with current product. Details: " + checkEx.getMessage();
msgArgs = new Object[] { dae };
}
log.error("Error occured.", dae);
throw ErrorHandler.getMappable(dae, msgCode, msg, param, msgArgs);
}
@Transactional
@Override
public CommandProcessingResult create(final JsonCommand command) {
try {
this.fromApiJsonDataValidator.validateForCreate(command.json());
final CurrentProduct product = this.currentProductAssembler.assemble(command);
final CurrentProductFacilityConfig facilityConfig =
this.currentProductAssembler.assembleAccountFacilityConfig(command, product);
final Set<CurrentProductFacilityLimit> facilityLimits =
this.currentProductAssembler.assembleAccountFacilityLimits(command, product);
final CurrentProductDepositLimitKyc depositLimitKyc =
this.currentProductAssembler.assembleDepositLimitKyc(command, product);
this.currentProductRepository.saveAndFlush(product);
if (facilityConfig != null) {
this.facilityConfigRepository.saveAndFlush(facilityConfig);
}
if (!facilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(facilityLimits);
}
if (depositLimitKyc != null) {
this.depositLimitKycRepository.saveAndFlush(depositLimitKyc);
}
this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
DepositAccountType.SAVINGS_DEPOSIT);
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.build();
} catch (final DataAccessException e) {
handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
handleDataIntegrityIssues(command, ExceptionUtils.getRootCause(dve.getCause()), dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult update(final Long productId, final JsonCommand command) {
try {
this.context.authenticatedUser();
final CurrentProduct product = this.currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
this.fromApiJsonDataValidator.validateForUpdate(command.json(), product);
final Map<String, Object> changes = product.update(command);
if (changes.containsKey(chargesParamName)) {
final Set<Charge> charges = this.currentProductAssembler.assembleListOfCurrentProductCharges(command,
product.currency().getCode());
final boolean updated = product.update(charges);
if (!updated) {
changes.remove(chargesParamName);
}
}
if (changes.containsKey(taxGroupIdParamName)) {
final TaxGroup taxGroup = this.currentProductAssembler.assembleTaxGroup(command);
product.setTaxGroup(taxGroup);
if (product.withHoldTax() && product.getTaxGroup() == null) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors)
.resource(DepositsApiConstants.CURRENT_PRODUCT_RESOURCE_NAME);
final Long taxGroupId = null;
baseDataValidator.reset().parameter(taxGroupIdParamName).value(taxGroupId).notBlank();
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
// Update facility config
final CurrentProductFacilityConfig existingFacilityConfig = product.getFacilityConfig();
final CurrentProductFacilityConfig newFacilityConfig =
this.currentProductAssembler.assembleAccountFacilityConfig(command, product);
if (newFacilityConfig != null) {
if (existingFacilityConfig != null) {
existingFacilityConfig.updateFrom(newFacilityConfig);
this.facilityConfigRepository.saveAndFlush(existingFacilityConfig);
} else {
this.facilityConfigRepository.saveAndFlush(newFacilityConfig);
}
} else if (existingFacilityConfig != null) {
this.facilityConfigRepository.delete(existingFacilityConfig);
}
// Update facility limits (delete old, insert new)
final Set<CurrentProductFacilityLimit> newFacilityLimits =
this.currentProductAssembler.assembleAccountFacilityLimits(command, product);
if (!newFacilityLimits.isEmpty() || command.hasParameter("accountFacilityLimits")) {
if (product.getFacilityLimits() != null && !product.getFacilityLimits().isEmpty()) {
this.facilityLimitRepository.deleteAll(product.getFacilityLimits());
product.getFacilityLimits().clear();
}
if (!newFacilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(newFacilityLimits);
product.setFacilityLimits(newFacilityLimits);
}
}
// Update deposit limit KYC
final CurrentProductDepositLimitKyc existingDepositLimitKyc = product.getDepositLimitKyc();
final CurrentProductDepositLimitKyc newDepositLimitKyc =
this.currentProductAssembler.assembleDepositLimitKyc(command, product);
if (newDepositLimitKyc != null) {
if (existingDepositLimitKyc != null) {
existingDepositLimitKyc.updateFrom(newDepositLimitKyc);
this.depositLimitKycRepository.saveAndFlush(existingDepositLimitKyc);
} else {
this.depositLimitKycRepository.saveAndFlush(newDepositLimitKyc);
}
} else if (existingDepositLimitKyc != null) {
this.depositLimitKycRepository.delete(existingDepositLimitKyc);
}
// accounting related changes
final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
.updateSavingsProductToGLAccountMapping(product.getId(), command, accountingTypeChanged, product.getAccountingType(),
DepositAccountType.SAVINGS_DEPOSIT);
changes.putAll(accountingMappingChanges);
if (!changes.isEmpty()) {
this.currentProductRepository.saveAndFlush(product);
}
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.with(changes).build();
} catch (final DataAccessException e) {
handleDataIntegrityIssues(command, e.getMostSpecificCause(), e);
return CommandProcessingResult.empty();
} catch (final PersistenceException dve) {
handleDataIntegrityIssues(command, ExceptionUtils.getRootCause(dve.getCause()), dve);
return CommandProcessingResult.empty();
}
}
@Transactional
@Override
public CommandProcessingResult delete(final Long productId) {
this.context.authenticatedUser();
final CurrentProduct product = this.currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
this.currentProductRepository.delete(product);
return new CommandProcessingResultBuilder() //
.withEntityId(product.getId()) //
.build();
}
}
@@ -0,0 +1,64 @@
/**
* 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.currentproduct.starter;
import org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingWritePlatformService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.currentproduct.data.CurrentProductDataValidator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductAssembler;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityConfigRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductFacilityLimitRepository;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductReadPlatformService;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductReadPlatformServiceImpl;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductWritePlatformService;
import org.apache.fineract.portfolio.currentproduct.service.CurrentProductWritePlatformServiceJpaRepositoryImpl;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
@Configuration
public class CurrentProductConfiguration {
@Bean
@ConditionalOnMissingBean(CurrentProductReadPlatformService.class)
public CurrentProductReadPlatformService currentProductReadPlatformService(PlatformSecurityContext context,
JdbcTemplate jdbcTemplate, CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
return new CurrentProductReadPlatformServiceImpl(context, jdbcTemplate, currentFacilityConfigRepository,
currentFacilityLimitRepository, currentDepositLimitKycRepository);
}
@Bean
@ConditionalOnMissingBean(CurrentProductWritePlatformService.class)
public CurrentProductWritePlatformService currentProductWritePlatformService(PlatformSecurityContext context,
CurrentProductRepository currentProductRepository, CurrentProductDataValidator currentProductDataValidator,
CurrentProductAssembler currentProductAssembler,
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
CurrentProductFacilityConfigRepository currentFacilityConfigRepository,
CurrentProductFacilityLimitRepository currentFacilityLimitRepository,
CurrentProductDepositLimitKycRepository currentDepositLimitKycRepository) {
return new CurrentProductWritePlatformServiceJpaRepositoryImpl(context, currentProductRepository, currentProductDataValidator,
currentProductAssembler, accountMappingWritePlatformService, currentFacilityConfigRepository,
currentFacilityLimitRepository, currentDepositLimitKycRepository);
}
}