update saving product code

This commit is contained in:
Nitesh Yadav
2026-04-13 13:26:33 +05:30
parent 011239f399
commit d7821c7824
56 changed files with 64009 additions and 21179 deletions
@@ -36,6 +36,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
@@ -49,6 +50,8 @@ import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@@ -62,6 +65,7 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
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;
@@ -94,6 +98,7 @@ public class CurrentProductsApiResource {
private final PaymentTypeReadService paymentTypeReadPlatformService;
private final TaxReadPlatformService taxReadPlatformService;
private final ConfigurationDomainService configurationDomainService;
private final CodeValueReadPlatformService codeValueReadPlatformService;
@POST
@Consumes({ MediaType.APPLICATION_JSON })
@@ -181,6 +186,21 @@ public class CurrentProductsApiResource {
return toApiJsonSerializer.serialize(settings, savingProduct, SavingsApiSetConstants.SAVINGS_PRODUCT_RESPONSE_DATA_PARAMETERS);
}
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
@DELETE
@Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -221,11 +241,13 @@ public class CurrentProductsApiResource {
.retrieveInterestPostingPeriodTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOptions = dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions();
final Collection<EnumOptionData> lockinPeriodFrequencyTypeOptions = dropdownReadPlatformService
.retrieveLockinPeriodFrequencyTypeOptions();
final Collection<EnumOptionData> withdrawalFeeTypeOptions = dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final Collection<PaymentTypeData> paymentTypeOptions = paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -247,16 +269,18 @@ public class CurrentProductsApiResource {
SavingsProductData savingsProductToReturn = null;
if (savingsProduct != null) {
savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, accountMappingForPayment);
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption,
interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, null, null, null,
accountMappingForPayment);
} else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions,
accountMappingForPayment);
interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
penaltyOptions, taxGroupOptions, null, null, null, accountMappingForPayment);
}
return savingsProductToReturn;
@@ -40,6 +40,7 @@ import jakarta.ws.rs.core.UriInfo;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
@@ -56,6 +57,8 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
@@ -91,6 +94,7 @@ public class SavingsProductsApiResource {
private final CurrencyReadPlatformService currencyReadPlatformService;
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsProductData> toApiJsonSerializer;
private final CodeValueReadPlatformService codeValueReadPlatformService;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final AccountingDropdownReadPlatformService accountingDropdownReadPlatformService;
@@ -242,6 +246,7 @@ public class SavingsProductsApiResource {
final Collection<EnumOptionData> interestCalculationTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationTypeOptions();
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
final Collection<EnumOptionData> interestCalculationDaysInYearTypeOptions = this.dropdownReadPlatformService
.retrieveInterestCalculationDaysInYearTypeOptions();
@@ -250,6 +255,7 @@ public class SavingsProductsApiResource {
.retrieveLockinPeriodFrequencyTypeOptions();
final Collection<EnumOptionData> withdrawalFeeTypeOptions = this.dropdownReadPlatformService.retrievewithdrawalFeeTypeOptions();
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
final Collection<PaymentTypeData> paymentTypeOptions = this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
@@ -268,24 +274,49 @@ public class SavingsProductsApiResource {
Collection<ChargeData> penaltyOptions = this.chargeReadPlatformService.retrieveSavingsApplicablePenalties();
penaltyOptions = CollectionUtils.isEmpty(penaltyOptions) ? null : penaltyOptions;
final Collection<TaxGroupData> taxGroupOptions = this.taxReadPlatformService.retrieveTaxGroupsForLookUp();
final List<CodeValueData> yesNoOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_ACCOUNT_TYPE));
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_CUSTOMER_SEGMENT));
SavingsProductData savingsProductToReturn = null;
if (savingsProduct != null) {
savingsProductToReturn = SavingsProductData.withTemplate(savingsProduct, currencyOptions, interestCompoundingPeriodTypeOptions,
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions,
accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, accountMappingForPayment);
interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationTypeOption,
interestCalculationDaysInYearTypeOptions,
lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions,
accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions,
accountTypeOptions, customerSegmentOptions,
accountMappingForPayment);
} else {
savingsProductToReturn = SavingsProductData.template(currency, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, accountingRule, currencyOptions,
interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions,
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions,
interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions,
accountMappingForPayment);
}
return savingsProductToReturn;
}
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
.toList();
}
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
.map(codeValue -> new EnumOptionData(codeValue.getId(),
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
codeValue.getName()))
.toList();
}
@DELETE
@Path("{productId}")
@Consumes({ MediaType.APPLICATION_JSON })
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.api;
import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
@@ -82,6 +83,7 @@ final class SavingsProductsApiResourceSwagger {
public Boolean withHoldTax;
@Schema(example = "false")
public Boolean isDormancyTrackingActive;
public List<Map<String, Object>> otherGlMappings;
}
@Schema(description = "PostSavingsProductsResponse")
@@ -128,6 +130,25 @@ final class SavingsProductsApiResourceSwagger {
public PutSavingsChanges changes;
}
@Schema(description = "GetSavingsProductsProductClassificationOptions")
static final class GetSavingsProductsProductClassificationOptions {
private GetSavingsProductsProductClassificationOptions() {}
@Schema(example = "1")
public Long id;
@Schema(example = "Savings Account Minor")
public String name;
@Schema(example = "1")
public Integer position;
@Schema(example = "Savings Account Minor")
public String description;
@Schema(example = "true")
public Boolean active;
@Schema(example = "false")
public Boolean mandatory;
}
@Schema(description = "GetSavingsProductsResponse")
public static final class GetSavingsProductsResponse {
@@ -377,6 +398,7 @@ final class SavingsProductsApiResourceSwagger {
public Set<GetSavingsProductsPenaltyToIncomeAccountMappings> penaltyToIncomeAccountMappings;
@Schema(example = "[]")
public List<Long> charges;
public List<Map<String, Object>> otherGlMappings;
}
@Schema(description = "GetSavingsProductsTemplateResponse")
@@ -408,6 +430,30 @@ final class SavingsProductsApiResourceSwagger {
public String value;
}
static final class GetSavingsProductsPeriodFrequencyOptions {
private GetSavingsProductsPeriodFrequencyOptions() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "periodFrequencyType.monthly")
public String code;
@Schema(example = "Monthly")
public String value;
}
static final class GetSavingsProductsInterestCalculationTypeOption {
private GetSavingsProductsInterestCalculationTypeOption() {}
@Schema(example = "1")
public Integer id;
@Schema(example = "interestCalculationTypeOption.compounding")
public String code;
@Schema(example = "Compounding")
public String value;
}
static final class GetSavingsProductsWithdrawalFeeTypeOptions {
private GetSavingsProductsWithdrawalFeeTypeOptions() {}
@@ -677,13 +723,18 @@ final class SavingsProductsApiResourceSwagger {
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCompoundingPeriodType> interestCompoundingPeriodTypeOptions;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestPostingPeriodType> interestPostingPeriodTypeOptions;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationType> interestCalculationTypeOptions;
public Set<GetSavingsProductsInterestCalculationTypeOption> interestCalculationTypeOption;
public Set<GetSavingsProductsResponse.GetSavingsProductsInterestCalculationDaysInYearType> interestCalculationDaysInYearTypeOptions;
public Set<GetSavingsProductsLockinPeriodFrequencyTypeOptions> lockinPeriodFrequencyTypeOptions;
public Set<GetSavingsProductsWithdrawalFeeTypeOptions> withdrawalFeeTypeOptions;
public Set<GetSavingsProductsPeriodFrequencyOptions> periodFrequencyOptions;
public Set<GetSavingsProductsPaymentTypeOptions> paymentTypeOptions;
public Set<GetSavingsProductsTemplateAccountingRule> accountingRuleOptions;
public GetSavingsProductsAccountingMappingOptions accountingMappingOptions;
public Set<GetSavingsProductsChargeOptions> chargeOptions;
public Set<GetSavingsProductsProductClassificationOptions> yesNoOptions;
public Set<GetSavingsProductsProductClassificationOptions> accountTypeOptions;
public Set<GetSavingsProductsProductClassificationOptions> customerSegmentOptions;
public GetSavingsProductsResponse.GetSavingsCurrency accountMapping;
}
@@ -18,10 +18,16 @@
*/
package org.apache.fineract.portfolio.savings.service;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import java.math.BigDecimal;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.accounting.common.AccountingEnumerations;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
@@ -32,6 +38,12 @@ import org.apache.fineract.infrastructure.security.service.PlatformSecurityConte
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.data.SavingsProductData;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
import org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKyc;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfigRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.tax.data.TaxGroupData;
import org.springframework.dao.EmptyResultDataAccessException;
@@ -46,6 +58,11 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
private final SavingProductMapper savingsProductRowMapper = new SavingProductMapper();
private final SavingProductLookupMapper savingsProductLookupsRowMapper = new SavingProductLookupMapper();
private final FineractEntityAccessUtil fineractEntityAccessUtil;
private final SavingsProductFacilityConfigRepository facilityConfigRepository;
private final SavingsProductFacilityLimitRepository facilityLimitRepository;
private final SavingsProductDepositLimitKycRepository depositLimitKycRepository;
private static final Gson GSON = new Gson();
private static final Type OTHER_GL_MAPPINGS_TYPE = new TypeToken<List<Map<String, Object>>>() {}.getType();
@Override
public Collection<SavingsProductData> retrieveAll() {
@@ -87,8 +104,66 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
try {
this.context.authenticatedUser();
final String sql = "select " + this.savingsProductRowMapper.schema() + " where sp.id = ? and sp.deposit_type_enum = ?";
return this.jdbcTemplate.queryForObject(sql, this.savingsProductRowMapper, // NOSONAR
SavingsProductData product = this.jdbcTemplate.queryForObject(sql, this.savingsProductRowMapper, // NOSONAR
new Object[] { savingProductId, DepositAccountType.SAVINGS_DEPOSIT.getValue() });
// Fetch related data using repositories
if (product != null) {
// Fetch Facility Config using repository
SavingsProductFacilityConfig facilityConfig = facilityConfigRepository.findBySavingsProductId(savingProductId);
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 using repository (1-to-Many Array)
List<SavingsProductFacilityLimit> facilityLimits = facilityLimitRepository.findBySavingsProductId(savingProductId);
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 using repository
SavingsProductDepositLimitKyc depositLimitKyc = depositLimitKycRepository.findBySavingsProductId(savingProductId);
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()));
depositLimitKycMap.put("smallSaving", Map.of(
"kycType", depositLimitKyc.getSmallSavingKycType() != null ? depositLimitKyc.getSmallSavingKycType() : "",
"limit", depositLimitKyc.getSmallSavingLimit()));
product.setDepositLimitKyc(depositLimitKycMap);
}
}
return product;
} catch (final EmptyResultDataAccessException e) {
throw new SavingsProductNotFoundException(savingProductId, e);
}
@@ -99,7 +174,7 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
private final String schemaSql;
SavingProductMapper() {
final StringBuilder sqlBuilder = new StringBuilder(400);
final StringBuilder sqlBuilder = new StringBuilder(1000);
sqlBuilder.append("sp.id as id, sp.name as name, sp.short_name as shortName, sp.description as description, ");
sqlBuilder.append(
"sp.currency_code as currencyCode, sp.currency_digits as currencyDigits, sp.currency_multiplesof as inMultiplesOf, ");
@@ -129,7 +204,20 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive,");
sqlBuilder.append("sp.days_to_inactive as daysToInactive,");
sqlBuilder.append("sp.days_to_dormancy as daysToDormancy,");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat ");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
sqlBuilder.append("sp.other_gl_mappings as otherGlMappings, ");
// Enhanced Field Selections
sqlBuilder.append("sp.account_type as accountType, sp.customer_segment as customerSegment, sp.product_code as productCode, ");
sqlBuilder.append("sp.effective_date as effectiveDate, sp.effective_to as effectiveTo, sp.end_date as endDate, ");
sqlBuilder.append("sp.introducer_required as introducerRequired, sp.share_linking_enabled as shareLinkingEnabled, ");
sqlBuilder.append("sp.nomination_facility as nominationFacility, sp.dormancy_alert as dormancyAlert, ");
sqlBuilder.append("sp.interest_calculate_selector as interestCalculateSelector, sp.average_period as averagePeriod, ");
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, sp.last_interest_date as lastInterestDate, ");
sqlBuilder.append("sp.enable_lockin_period as enableLockinPeriod, ");
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing ");
sqlBuilder.append("from m_savings_product sp ");
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id ");
@@ -212,13 +300,37 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
final Long daysToInactive = JdbcSupport.getLong(rs, "daysToInactive");
final Long daysToDormancy = JdbcSupport.getLong(rs, "daysToDormancy");
final Long daysToEscheat = JdbcSupport.getLong(rs, "daysToEscheat");
final List<Map<String, Object>> otherGlMappings = parseOtherGlMappings(rs.getString("otherGlMappings"));
return SavingsProductData.instance(id, name, shortName, description, currency, nominalAnnualInterestRate,
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, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat);
withHoldTax, taxGroupData, null, null, null, isDormancyTrackingActive, daysToInactive, daysToDormancy,
daysToEscheat, 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"));
return productData;
}
}
@@ -238,6 +350,13 @@ public class SavingsProductReadPlatformServiceImpl implements SavingsProductRead
}
}
private static List<Map<String, Object>> parseOtherGlMappings(final String otherGlMappingsJson) {
if (otherGlMappingsJson == null || otherGlMappingsJson.isBlank()) {
return null;
}
return GSON.fromJson(otherGlMappingsJson, OTHER_GL_MAPPINGS_TYPE);
}
@Override
public Collection<SavingsProductData> retrieveAllForLookupByType(Boolean isOverdraftType) {
String sql = "select " + this.savingsProductLookupsRowMapper.schema();
@@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -46,11 +47,18 @@ import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsProductDataValidator;
import org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKyc;
import org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfigRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
import org.apache.fineract.portfolio.savings.domain.SavingsProductAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductAssembler;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.apache.fineract.portfolio.tax.domain.TaxGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;
@@ -64,6 +72,9 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
private final SavingsProductAssembler savingsProductAssembler;
private final ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService;
private final FineractEntityAccessUtil fineractEntityAccessUtil;
private final SavingsProductFacilityConfigRepository facilityConfigRepository;
private final SavingsProductFacilityLimitRepository facilityLimitRepository;
private final SavingsProductDepositLimitKycRepository depositLimitKycRepository;
/*
* Guaranteed to throw an exception no matter what the data integrity issue is.
@@ -86,8 +97,15 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
msg = "Savings product with short name `" + shortName + "` already exists";
param = "shortName";
msgArgs = new Object[] { shortName, dae };
} else if (checkEx.getMessage().contains("product_code") || checkEx.getMessage().contains("sp_unq_product_code")) {
final String productCode = command.stringValueOfParameterNamed("productCode");
msgCode += ".duplicate.product.code";
msg = "Savings 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 savings product. Details: " + checkEx.getMessage();
msgArgs = new Object[] { dae };
}
log.error("Error occured.", dae);
@@ -103,8 +121,29 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
final SavingsProduct product = this.savingsProductAssembler.assemble(command);
// Assemble and save new complex objects
final SavingsProductFacilityConfig facilityConfig =
this.savingsProductAssembler.assembleAccountFacilityConfig(command, product);
final Set<SavingsProductFacilityLimit> facilityLimits =
this.savingsProductAssembler.assembleAccountFacilityLimits(command, product);
final SavingsProductDepositLimitKyc depositLimitKyc =
this.savingsProductAssembler.assembleDepositLimitKyc(command, product);
this.savingProductRepository.saveAndFlush(product);
// Save related entities
if (facilityConfig != null) {
this.facilityConfigRepository.saveAndFlush(facilityConfig);
}
if (!facilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(facilityLimits);
}
if (depositLimitKyc != null) {
this.depositLimitKycRepository.saveAndFlush(depositLimitKyc);
}
// save accounting mappings
this.accountMappingWritePlatformService.createSavingProductToGLAccountMapping(product.getId(), command,
DepositAccountType.SAVINGS_DEPOSIT);
@@ -162,6 +201,61 @@ public class SavingsProductWritePlatformServiceJpaRepositoryImpl implements Savi
}
}
// Update facility config
final SavingsProductFacilityConfig existingFacilityConfig = product.getFacilityConfig();
final SavingsProductFacilityConfig newFacilityConfig =
this.savingsProductAssembler.assembleAccountFacilityConfig(command, product);
if (newFacilityConfig != null) {
if (existingFacilityConfig != null) {
// Update existing config
existingFacilityConfig.updateFrom(newFacilityConfig);
this.facilityConfigRepository.saveAndFlush(existingFacilityConfig);
} else {
// Create new config
this.facilityConfigRepository.saveAndFlush(newFacilityConfig);
}
} else if (existingFacilityConfig != null) {
// Delete existing config if not provided in update
this.facilityConfigRepository.delete(existingFacilityConfig);
}
// Update facility limits (delete old, insert new)
final Set<SavingsProductFacilityLimit> newFacilityLimits =
this.savingsProductAssembler.assembleAccountFacilityLimits(command, product);
if (!newFacilityLimits.isEmpty() || command.hasParameter("accountFacilityLimits")) {
// Delete existing limits
if (product.getFacilityLimits() != null && !product.getFacilityLimits().isEmpty()) {
this.facilityLimitRepository.deleteAll(product.getFacilityLimits());
product.getFacilityLimits().clear();
}
// Save new limits
if (!newFacilityLimits.isEmpty()) {
this.facilityLimitRepository.saveAll(newFacilityLimits);
product.setFacilityLimits(newFacilityLimits);
}
}
// Update deposit limit KYC
final SavingsProductDepositLimitKyc existingDepositLimitKyc = product.getDepositLimitKyc();
final SavingsProductDepositLimitKyc newDepositLimitKyc =
this.savingsProductAssembler.assembleDepositLimitKyc(command, product);
if (newDepositLimitKyc != null) {
if (existingDepositLimitKyc != null) {
// Update existing KYC
existingDepositLimitKyc.updateFrom(newDepositLimitKyc);
this.depositLimitKycRepository.saveAndFlush(existingDepositLimitKyc);
} else {
// Create new KYC
this.depositLimitKycRepository.saveAndFlush(newDepositLimitKyc);
}
} else if (existingDepositLimitKyc != null) {
// Delete existing KYC if not provided in update
this.depositLimitKycRepository.delete(existingDepositLimitKyc);
}
// accounting related changes
final boolean accountingTypeChanged = changes.containsKey(accountingRuleParamName);
final Map<String, Object> accountingMappingChanges = this.accountMappingWritePlatformService
@@ -91,6 +91,11 @@ import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrap
import org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsHelper;
import org.apache.fineract.portfolio.savings.domain.SavingsProductAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKycRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfigRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService;
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
@@ -414,19 +419,27 @@ public class SavingsConfiguration {
@Bean
@ConditionalOnMissingBean(SavingsProductReadPlatformService.class)
public SavingsProductReadPlatformService savingsProductReadPlatformService(PlatformSecurityContext context, JdbcTemplate jdbcTemplate,
FineractEntityAccessUtil fineractEntityAccessUtil) {
return new SavingsProductReadPlatformServiceImpl(context, jdbcTemplate, fineractEntityAccessUtil);
FineractEntityAccessUtil fineractEntityAccessUtil, SavingsProductFacilityConfigRepository facilityConfigRepository,
SavingsProductFacilityLimitRepository facilityLimitRepository, SavingsProductDepositLimitKycRepository depositLimitKycRepository) {
return new SavingsProductReadPlatformServiceImpl(context, jdbcTemplate, fineractEntityAccessUtil, facilityConfigRepository,
facilityLimitRepository, depositLimitKycRepository);
}
@Bean
@ConditionalOnMissingBean(SavingsProductWritePlatformService.class)
public SavingsProductWritePlatformService savingsProductWritePlatformService(PlatformSecurityContext context,
SavingsProductRepository savingProductRepository, SavingsProductDataValidator fromApiJsonDataValidator,
public SavingsProductWritePlatformService savingsProductWritePlatformService(
PlatformSecurityContext context,
SavingsProductRepository savingProductRepository,
SavingsProductDataValidator fromApiJsonDataValidator,
SavingsProductAssembler savingsProductAssembler,
ProductToGLAccountMappingWritePlatformService accountMappingWritePlatformService,
FineractEntityAccessUtil fineractEntityAccessUtil) {
FineractEntityAccessUtil fineractEntityAccessUtil,
SavingsProductFacilityConfigRepository facilityConfigRepository,
SavingsProductFacilityLimitRepository facilityLimitRepository,
SavingsProductDepositLimitKycRepository depositLimitKycRepository) {
return new SavingsProductWritePlatformServiceJpaRepositoryImpl(context, savingProductRepository, fromApiJsonDataValidator,
savingsProductAssembler, accountMappingWritePlatformService, fineractEntityAccessUtil);
savingsProductAssembler, accountMappingWritePlatformService, fineractEntityAccessUtil, facilityConfigRepository,
facilityLimitRepository, depositLimitKycRepository);
}
@Bean
@@ -44,7 +44,7 @@ fineract.security.oauth2.client.registrations.frontend-client.require-authorizat
fineract.tenant.host=${FINERACT_DEFAULT_TENANTDB_HOSTNAME:localhost}
fineract.tenant.port=${FINERACT_DEFAULT_TENANTDB_PORT:5432}
fineract.tenant.username=${FINERACT_DEFAULT_TENANTDB_UID:postgres}
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:root}
fineract.tenant.password=${FINERACT_DEFAULT_TENANTDB_PWD:313863}
fineract.tenant.parameters=${FINERACT_DEFAULT_TENANTDB_CONN_PARAMS:}
fineract.tenant.timezone=${FINERACT_DEFAULT_TENANTDB_TIMEZONE:Asia/Kolkata}
fineract.tenant.identifier=${FINERACT_DEFAULT_TENANTDB_IDENTIFIER:default}
@@ -405,7 +405,7 @@ server.tomcat.mbeanregistry.enabled=${FINERACT_SERVER_TOMCAT_MBEANREGISTRY_ENABL
spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver}
spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants}
spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres}
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:root}
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:313863}
spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3}
spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10}
spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
@@ -268,4 +268,5 @@
<include file="parts/0247_add_huf_client_classification.xml" relativeToChangelogFile="true"/>
<include file="parts/0248_add_cin_din_verification_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0249_add_non_person_extended_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0251_add_savings_product_enhanced_fields.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet author="fineract" id="0250_add_period_frequency_options">
<insert tableName="m_code">
<column name="code_name" value="PeriodFrequency"/>
<column name="is_system_defined" valueBoolean="true"/>
</insert>
<insert tableName="m_code_value">
<column name="code_id" valueComputed="(SELECT id FROM m_code WHERE code_name = 'PeriodFrequency')"/>
<column name="code_value" value="Monthly"/>
<column name="code_description" value="Monthly"/>
<column name="order_position" valueNumeric="1"/>
<column name="is_active" valueBoolean="true"/>
<column name="is_mandatory" valueBoolean="false"/>
</insert>
<insert tableName="m_code_value">
<column name="code_id" valueComputed="(SELECT id FROM m_code WHERE code_name = 'PeriodFrequency')"/>
<column name="code_value" value="Quarterly"/>
<column name="code_description" value="Quarterly"/>
<column name="order_position" valueNumeric="2"/>
<column name="is_active" valueBoolean="true"/>
<column name="is_mandatory" valueBoolean="false"/>
</insert>
<insert tableName="m_code_value">
<column name="code_id" valueComputed="(SELECT id FROM m_code WHERE code_name = 'PeriodFrequency')"/>
<column name="code_value" value="Half Yearly"/>
<column name="code_description" value="Half Yearly"/>
<column name="order_position" valueNumeric="3"/>
<column name="is_active" valueBoolean="true"/>
<column name="is_mandatory" valueBoolean="false"/>
</insert>
<insert tableName="m_code_value">
<column name="code_id" valueComputed="(SELECT id FROM m_code WHERE code_name = 'PeriodFrequency')"/>
<column name="code_value" value="Yearly"/>
<column name="code_description" value="Yearly"/>
<column name="order_position" valueNumeric="4"/>
<column name="is_active" valueBoolean="true"/>
<column name="is_mandatory" valueBoolean="false"/>
</insert>
</changeSet>
</databaseChangeLog>
@@ -0,0 +1,232 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<!-- Add new columns to m_savings_product table -->
<changeSet id="0251_add_savings_product_enhanced_fields" author="system">
<addColumn tableName="m_savings_product">
<column name="account_type" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="customer_segment" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="product_code" type="varchar(20)">
<constraints nullable="true" unique="true"/>
</column>
<column name="effective_date" type="date">
<constraints nullable="true"/>
</column>
<column name="effective_to" type="date">
<constraints nullable="true"/>
</column>
<column name="end_date" type="date">
<constraints nullable="true"/>
</column>
<column name="introducer_required" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="share_linking_enabled" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="nomination_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="dormancy_alert" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="interest_calculate_selector" type="int">
<constraints nullable="true"/>
</column>
<column name="average_period" type="varchar(20)">
<constraints nullable="true"/>
</column>
<column name="auto_post" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="frequency_fix_day" type="int">
<constraints nullable="true"/>
</column>
<column name="last_interest_date" type="date">
<constraints nullable="true"/>
</column>
<column name="enable_lockin_period" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
<!-- Create m_savings_product_facility_config table -->
<changeSet id="0252_create_savings_product_facility_config_table" author="system">
<createTable tableName="m_savings_product_facility_config">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="savings_product_id" type="bigint">
<constraints nullable="false" unique="true"/>
</column>
<column name="atm_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="atm_withdrawal_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="upi_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="pos_transactions_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="cheque_book" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="imps_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="sms_alerts" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="bbps_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="insurance_cover" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="bepg_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="neft_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="rtgs_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="inter_bank_total_cap_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="withdrawal_facility" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="deposit_facility_kyc" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="virtual_debit_card" type="boolean" defaultValue="false">
<constraints nullable="false"/>
</column>
<column name="created_date" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint
baseTableName="m_savings_product_facility_config"
baseColumnNames="savings_product_id"
referencedTableName="m_savings_product"
referencedColumnNames="id"
constraintName="fk_savings_product_facility_config_product_id"
onDelete="CASCADE"/>
</changeSet>
<!-- Create m_savings_product_facility_limits table -->
<changeSet id="0253_create_savings_product_facility_limits_table" author="system">
<createTable tableName="m_savings_product_facility_limits">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="savings_product_id" type="bigint">
<constraints nullable="false"/>
</column>
<column name="limit_type" type="varchar(100)">
<constraints nullable="false"/>
</column>
<column name="general_limit" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="minor_account_limit" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="small_saving_limit" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="created_date" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint
baseTableName="m_savings_product_facility_limits"
baseColumnNames="savings_product_id"
referencedTableName="m_savings_product"
referencedColumnNames="id"
constraintName="fk_savings_product_facility_limits_product_id"
onDelete="CASCADE"/>
<createIndex tableName="m_savings_product_facility_limits" indexName="idx_savings_product_facility_limits_product_type">
<column name="savings_product_id"/>
<column name="limit_type"/>
</createIndex>
</changeSet>
<!-- Create m_savings_product_deposit_limit_kyc table -->
<changeSet id="0254_create_savings_product_deposit_limit_kyc_table" author="system">
<createTable tableName="m_savings_product_deposit_limit_kyc">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="savings_product_id" type="bigint">
<constraints nullable="false" unique="true"/>
</column>
<column name="general_kyc_type" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="general_limit" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="minor_kyc_type" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="minor_limit" type="bigint">
<constraints nullable="true"/>
</column>
<column name="small_saving_kyc_type" type="varchar(50)">
<constraints nullable="true"/>
</column>
<column name="small_saving_limit" type="bigint">
<constraints nullable="true"/>
</column>
<column name="created_date" type="datetime" defaultValueComputed="CURRENT_TIMESTAMP">
<constraints nullable="false"/>
</column>
</createTable>
<addForeignKeyConstraint
baseTableName="m_savings_product_deposit_limit_kyc"
baseColumnNames="savings_product_id"
referencedTableName="m_savings_product"
referencedColumnNames="id"
constraintName="fk_savings_product_deposit_limit_kyc_product_id"
onDelete="CASCADE"/>
</changeSet>
</databaseChangeLog>
@@ -260,6 +260,9 @@
<class>org.apache.fineract.portfolio.savings.domain.SavingsAccountTransactionTaxDetails</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsOfficerAssignmentHistory</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProduct</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKyc</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit</class>
<!-- Progressive Loan Module Entities -->
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance</class>
@@ -4667,6 +4667,7 @@ CREATE TABLE IF NOT EXISTS `m_savings_product` (
`days_to_inactive` INT DEFAULT NULL,
`days_to_dormancy` INT DEFAULT NULL,
`days_to_escheat` INT DEFAULT NULL,
`other_gl_mappings` LONGTEXT DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sp_unq_name` (`name`),
UNIQUE KEY `sp_unq_short_name` (`short_name`),
@@ -4918,6 +4918,7 @@ CREATE TABLE IF NOT EXISTS `m_savings_product` (
`days_to_inactive` INT DEFAULT NULL,
`days_to_dormancy` INT DEFAULT NULL,
`days_to_escheat` INT DEFAULT NULL,
`other_gl_mappings` LONGTEXT DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `sp_unq_name` (`name`),
UNIQUE KEY `sp_unq_short_name` (`short_name`),
@@ -4927,8 +4928,8 @@ CREATE TABLE IF NOT EXISTS `m_savings_product` (
-- Dumping data for table mifostenant-reference.m_savings_product: ~1 rows (approximately)
/*!40000 ALTER TABLE `m_savings_product` DISABLE KEYS */;
INSERT INTO `m_savings_product` (`id`, `name`, `short_name`, `description`, `deposit_type_enum`, `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`, `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`, `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `lockin_period_frequency`, `lockin_period_frequency_enum`, `accounting_type`, `withdrawal_fee_amount`, `withdrawal_fee_type_enum`, `withdrawal_fee_for_transfer`, `allow_overdraft`, `overdraft_limit`, `nominal_annual_interest_rate_overdraft`, `min_overdraft_for_interest_calculation`, `min_required_balance`, `enforce_min_required_balance`, `min_balance_for_interest_calculation`, `withhold_tax`, `tax_group_id`, `is_dormancy_tracking_active`, `days_to_inactive`, `days_to_dormancy`, `days_to_escheat`) VALUES
(1, 'Voluntary savings', 'VS', 'Save money', 100, 'USD', 2, 0, 9.500000, 1, 4, 1, 365, 1000.000000, 1.000000, 1, 2, NULL, NULL, 0, 0, NULL, 0.000000, 0.000000, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `m_savings_product` (`id`, `name`, `short_name`, `description`, `deposit_type_enum`, `currency_code`, `currency_digits`, `currency_multiplesof`, `nominal_annual_interest_rate`, `interest_compounding_period_enum`, `interest_posting_period_enum`, `interest_calculation_type_enum`, `interest_calculation_days_in_year_type_enum`, `min_required_opening_balance`, `lockin_period_frequency`, `lockin_period_frequency_enum`, `accounting_type`, `withdrawal_fee_amount`, `withdrawal_fee_type_enum`, `withdrawal_fee_for_transfer`, `allow_overdraft`, `overdraft_limit`, `nominal_annual_interest_rate_overdraft`, `min_overdraft_for_interest_calculation`, `min_required_balance`, `enforce_min_required_balance`, `min_balance_for_interest_calculation`, `withhold_tax`, `tax_group_id`, `is_dormancy_tracking_active`, `days_to_inactive`, `days_to_dormancy`, `days_to_escheat`, `other_gl_mappings`) VALUES
(1, 'Voluntary savings', 'VS', 'Save money', 100, 'USD', 2, 0, 9.500000, 1, 4, 1, 365, 1000.000000, 1.000000, 1, 2, NULL, NULL, 0, 0, NULL, 0.000000, 0.000000, NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL);
/*!40000 ALTER TABLE `m_savings_product` ENABLE KEYS */;