diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java index b7fd6726c..46ec6e4dc 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/DepositsApiConstants.java @@ -274,6 +274,46 @@ public final class DepositsApiConstants { fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); fixedDepositRequestData.addAll(PRECLOSURE_REQUEST_DATA_PARAMETERS); fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); + fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add("productCode"); + fixedDepositRequestData.add("fdType"); + fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + fixedDepositRequestData.add("effectiveDate"); + fixedDepositRequestData.add("effectiveTo"); + fixedDepositRequestData.add("endDate"); + fixedDepositRequestData.add("introducerRequired"); + fixedDepositRequestData.add("shareLinkingEnabled"); + fixedDepositRequestData.add("nominationFacility"); + fixedDepositRequestData.add("dormancyAlert"); + fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); + fixedDepositRequestData.add("interestCalculateSelector"); + fixedDepositRequestData.add("averagePeriod"); + fixedDepositRequestData.add("autoPost"); + fixedDepositRequestData.add("frequencyFixDay"); + fixedDepositRequestData.add("lastInterestDate"); + fixedDepositRequestData.add("enableLockinPeriod"); + fixedDepositRequestData.add("balanceFrequency"); + fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + fixedDepositRequestData.add("autoDebitFacility"); + fixedDepositRequestData.add("loanFacility"); + fixedDepositRequestData.add("prematureWithdrawalOption"); + fixedDepositRequestData.add("tenureFlexibility"); + fixedDepositRequestData.add("lienMarked"); + fixedDepositRequestData.add("withdrawalFeeForTransfers"); + fixedDepositRequestData.add("maturityInstructions"); + fixedDepositRequestData.add("creditMaturityProceeds"); + fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + fixedDepositRequestData.add("panelRequestApplicable"); + fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); + fixedDepositRequestData.add("deceasedSettlement"); + fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); + fixedDepositRequestData.add("escheatLiabilityId"); + fixedDepositRequestData.add("fdSlabs"); return fixedDepositRequestData; } @@ -282,6 +322,54 @@ public final class DepositsApiConstants { fixedDepositRequestData.addAll(DEPOSIT_PRODUCT_REQUEST_DATA_PARAMETERS); fixedDepositRequestData.addAll(PRECLOSURE_RESPONSE_DATA_PARAMETERS); fixedDepositRequestData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); + fixedDepositRequestData.add(SavingsApiConstants.minBalanceForInterestCalculationParamName); + fixedDepositRequestData.add("interestCalculationTypeOption"); + fixedDepositRequestData.add("periodFrequencyOptions"); + fixedDepositRequestData.add("yesNoOptions"); + fixedDepositRequestData.add("accountTypeOptions"); + fixedDepositRequestData.add("customerSegmentOptions"); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add("productCode"); + fixedDepositRequestData.add("fdType"); + fixedDepositRequestData.add(SavingsApiConstants.glAccountIdParamName); + fixedDepositRequestData.add("glAccountName"); + fixedDepositRequestData.add("effectiveDate"); + fixedDepositRequestData.add("effectiveTo"); + fixedDepositRequestData.add("endDate"); + fixedDepositRequestData.add("introducerRequired"); + fixedDepositRequestData.add("shareLinkingEnabled"); + fixedDepositRequestData.add("nominationFacility"); + fixedDepositRequestData.add("dormancyAlert"); + fixedDepositRequestData.add(SavingsApiConstants.isDormancyTrackingActiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToInactiveParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToDormancyParamName); + fixedDepositRequestData.add(SavingsApiConstants.daysToEscheatParamName); + fixedDepositRequestData.add("interestCalculateSelector"); + fixedDepositRequestData.add("averagePeriod"); + fixedDepositRequestData.add("autoPost"); + fixedDepositRequestData.add("frequencyFixDay"); + fixedDepositRequestData.add("lastInterestDate"); + fixedDepositRequestData.add("enableLockinPeriod"); + fixedDepositRequestData.add("balanceFrequency"); + fixedDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + fixedDepositRequestData.add("autoDebitFacility"); + fixedDepositRequestData.add("loanFacility"); + fixedDepositRequestData.add("prematureWithdrawalOption"); + fixedDepositRequestData.add("tenureFlexibility"); + fixedDepositRequestData.add("lienMarked"); + fixedDepositRequestData.add("withdrawalFeeForTransfers"); + fixedDepositRequestData.add("maturityInstructions"); + fixedDepositRequestData.add("creditMaturityProceeds"); + fixedDepositRequestData.add("penaltyGracePeriodMissedInstallments"); + fixedDepositRequestData.add("panelRequestApplicable"); + fixedDepositRequestData.add("noPenaltyNomineeHeirClosure"); + fixedDepositRequestData.add("deceasedSettlement"); + fixedDepositRequestData.add("allowDebitTxnAfterDueDate"); + fixedDepositRequestData.add("accountingMappings"); + fixedDepositRequestData.add("paymentChannelToFundSourceMappings"); + fixedDepositRequestData.add("feeToIncomeAccountMappings"); + fixedDepositRequestData.add("penaltyToIncomeAccountMappings"); return fixedDepositRequestData; } @@ -332,6 +420,7 @@ public final class DepositsApiConstants { recurringDepositRequestData.add("deceasedSettlement"); recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); recurringDepositRequestData.add("accountClosedZeroBalanceInDayClosing"); + recurringDepositRequestData.add("escheatLiabilityId"); // Custom CBS fields — Interest rate slabs (sent inline but ignored server-side; saved separately) recurringDepositRequestData.add("rdSlabs"); return recurringDepositRequestData; @@ -387,6 +476,11 @@ public final class DepositsApiConstants { recurringDepositRequestData.add("noPenaltyNomineeHeirClosure"); recurringDepositRequestData.add("deceasedSettlement"); recurringDepositRequestData.add("allowDebitTxnAfterDueDate"); + recurringDepositRequestData.add("escheatLiabilityId"); + recurringDepositRequestData.add("accountingMappings"); + recurringDepositRequestData.add("paymentChannelToFundSourceMappings"); + recurringDepositRequestData.add("feeToIncomeAccountMappings"); + recurringDepositRequestData.add("penaltyToIncomeAccountMappings"); return recurringDepositRequestData; } @@ -428,6 +522,27 @@ public final class DepositsApiConstants { fixedDepositRequestData.addAll(DEPOSIT_TERM_REQUEST_DATA_PARAMETERS); fixedDepositRequestData.add(linkedAccountParamName); fixedDepositRequestData.add(transferInterestToSavingsParamName); + fixedDepositRequestData.add("accountType"); + fixedDepositRequestData.add("customerSegment"); + fixedDepositRequestData.add(SavingsApiConstants.accountStatusParamName); + fixedDepositRequestData.add(SavingsApiConstants.modeOfOperationParamName); + fixedDepositRequestData.add(SavingsApiConstants.specialCategoryParamName); + fixedDepositRequestData.add(SavingsApiConstants.weakerSectionParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianNameParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianCifNumberParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianRelationshipParamName); + fixedDepositRequestData.add(SavingsApiConstants.guardianClientIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.minorDateOfBirthParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerNameParamName); + fixedDepositRequestData.add(SavingsApiConstants.introducerCifNumberParamName); + fixedDepositRequestData.add(SavingsApiConstants.nomineeParamName); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountIdParamName); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountTypeParamName); + fixedDepositRequestData.add(SavingsApiConstants.linkedAccountNoParamName); + fixedDepositRequestData.add(SavingsApiConstants.jointHoldersParamName); + fixedDepositRequestData.add(SavingsApiConstants.autoDebitFacilityParamName); + fixedDepositRequestData.add(SavingsApiConstants.nominationFacilityParamName); return fixedDepositRequestData; } @@ -438,6 +553,31 @@ public final class DepositsApiConstants { fixedDepositResponseData.addAll(DEPOSIT_TERM_RESPONSE_DATA_PARAMETERS); fixedDepositResponseData.add(linkedAccountParamName); fixedDepositResponseData.add(transferInterestToSavingsParamName); + fixedDepositResponseData.add("accountType"); + fixedDepositResponseData.add("customerSegment"); + fixedDepositResponseData.add("accountTypeOptions"); + fixedDepositResponseData.add("customerSegmentOptions"); + fixedDepositResponseData.add("productCode"); + fixedDepositResponseData.add("previewAccountNo"); + fixedDepositResponseData.add(SavingsApiConstants.accountStatusParamName); + fixedDepositResponseData.add(SavingsApiConstants.modeOfOperationParamName); + fixedDepositResponseData.add(SavingsApiConstants.specialCategoryParamName); + fixedDepositResponseData.add(SavingsApiConstants.weakerSectionParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianNameParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianCifNumberParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianRelationshipParamName); + fixedDepositResponseData.add(SavingsApiConstants.guardianClientIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.minorDateOfBirthParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerNameParamName); + fixedDepositResponseData.add(SavingsApiConstants.introducerCifNumberParamName); + fixedDepositResponseData.add(SavingsApiConstants.nomineeParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountIdParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountTypeParamName); + fixedDepositResponseData.add(SavingsApiConstants.linkedAccountNoParamName); + fixedDepositResponseData.add(SavingsApiConstants.jointHoldersParamName); + fixedDepositResponseData.add(SavingsApiConstants.nominationFacilityParamName); + fixedDepositResponseData.add(SavingsApiConstants.autoDebitFacilityParamName); return fixedDepositResponseData; } @@ -517,6 +657,10 @@ public final class DepositsApiConstants { recurringDepositResponseData.add("noPenaltyNomineeHeirClosure"); recurringDepositResponseData.add("deceasedSettlement"); recurringDepositResponseData.add("allowDebitTxnAfterDueDate"); + recurringDepositResponseData.add("accountingMappings"); + recurringDepositResponseData.add("paymentChannelToFundSourceMappings"); + recurringDepositResponseData.add("feeToIncomeAccountMappings"); + recurringDepositResponseData.add("penaltyToIncomeAccountMappings"); return recurringDepositResponseData; } diff --git a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index de328c0fb..8b6a61f18 100644 --- a/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-core/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -32,6 +32,8 @@ public class SavingsApiConstants { public static final String CURRENT_PRODUCT_CUSTOMER_SEGMENT = "CurrentProductCustomerSegment"; public static final String RD_PRODUCT_ACCOUNT_TYPE = "RecurringDepositProductAccountType"; public static final String RD_PRODUCT_CUSTOMER_SEGMENT = "RecurringDepositProductCustomerSegment"; + public static final String FD_PRODUCT_ACCOUNT_TYPE = "FixedDepositProductAccountType"; + public static final String FD_PRODUCT_CUSTOMER_SEGMENT = "FixedDepositProductCustomerSegment"; public static final String PERIOD_FREQUENCY = "PeriodFrequency"; public static final String INTEREST_CALCULATION_TYPE_OPTION = "InterestCalculationTypeOption"; diff --git a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentproduct/domain/CurrentProduct.java b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentproduct/domain/CurrentProduct.java index fad28ee69..3f9494a6a 100644 --- a/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentproduct/domain/CurrentProduct.java +++ b/fineract-current-product/src/main/java/org/apache/fineract/portfolio/currentproduct/domain/CurrentProduct.java @@ -226,7 +226,7 @@ public class CurrentProduct extends AbstractPersistableCustom { @Column(name = "customer_segment", length = 50) private String customerSegment; - @Column(name = "product_code", length = 20, unique = true) + @Column(name = "product_code", length = 20) private String productCode; @Column(name = "gl_account_id") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java index 874462dd9..eb24b989d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountAssociationsReadPlatformServiceImpl.java @@ -189,7 +189,16 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci @Override public PortfolioAccountData retriveSavingsAccount(final Long savingsId) { - String accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId); + String accountNo = null; + try { + accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId); + } catch (EmptyResultDataAccessException e) { + try { + accountNo = jdbcTemplate.queryForObject("select account_no from m_current_account where id = ?", String.class, savingsId); + } catch (EmptyResultDataAccessException ex) { + return null; + } + } return PortfolioAccountData.lookup(savingsId, accountNo); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentProductsApiResource.java index f32fed013..06082b591 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/CurrentProductsApiResource.java @@ -102,21 +102,6 @@ public class CurrentProductsApiResource { private final CodeValueReadPlatformService codeValueReadPlatformService; private final JdbcTemplate jdbcTemplate; - private String generateNextProductCode(final String tableName, final String prefix) { - try { - String maxCode = jdbcTemplate.queryForObject( - "SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?", - String.class, prefix + "%"); - if (maxCode == null || maxCode.isBlank()) { - return prefix + "001"; - } - String numStr = maxCode.substring(prefix.length()); - int num = Integer.parseInt(numStr); - return prefix + String.format("%03d", num + 1); - } catch (Exception e) { - return prefix + "001"; - } - } @POST @Consumes({ MediaType.APPLICATION_JSON }) @@ -306,7 +291,7 @@ public class CurrentProductsApiResource { interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions, accountMappingForPayment); - savingsProductToReturn.setProductCode(generateNextProductCode("m_current_product", "CA-IND-")); + // product code removed } return savingsProductToReturn; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FdInterestRateSaveRequest.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FdInterestRateSaveRequest.java new file mode 100644 index 000000000..c54951b4e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FdInterestRateSaveRequest.java @@ -0,0 +1,42 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.api; + +import java.math.BigDecimal; +import java.util.List; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +public class FdInterestRateSaveRequest { + + private String fdType; + private String versionLabel; + private String remarks; + private List slabs; + + @Getter + @Setter + @NoArgsConstructor + public static class SlabRequest { + private Integer srNo; + private String description; + private Integer periodTypeEnum; + private Integer fromPeriod; + private Integer toPeriod; + private BigDecimal annualInterestRate; + private BigDecimal seniorCitizenRate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java index 4cc92f669..9347d544b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResource.java @@ -54,9 +54,13 @@ import org.apache.fineract.commands.service.CommandWrapperBuilder; import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import java.util.Locale; import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.portfolio.savings.SavingsApiConstants; import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; +import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService; @@ -110,6 +114,8 @@ public class FixedDepositProductsApiResource { private final DropdownReadPlatformService dropdownReadPlatformService; private final PaymentTypeReadService paymentTypeReadPlatformService; private final TaxReadPlatformService taxReadPlatformService; + private final CodeValueReadPlatformService codeValueReadPlatformService; + private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate; @POST @Consumes({ MediaType.APPLICATION_JSON }) @@ -315,6 +321,16 @@ public class FixedDepositProductsApiResource { // interest rate chart template final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template(); + // CBS custom options + final Collection interestCalculationTypeOption = retrieveInterestCalculationTypeOption(); + final Collection periodFrequencyOptions = retrievePeriodFrequencyOptions(); + final List yesNoOptions = new ArrayList<>( + this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO)); + final List accountTypeOptions = new ArrayList<>( + this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE)); + final List customerSegmentOptions = new ArrayList<>( + this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT)); + FixedDepositProductData fixedDepositProductToReturn = null; if (savingsProduct != null) { fixedDepositProductToReturn = FixedDepositProductData.withTemplate(savingsProduct, currencyOptions, @@ -329,11 +345,34 @@ public class FixedDepositProductsApiResource { interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); + // product code removed } + fixedDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption); + fixedDepositProductToReturn.setPeriodFrequencyOptions(periodFrequencyOptions); + fixedDepositProductToReturn.setYesNoOptions(yesNoOptions); + fixedDepositProductToReturn.setAccountTypeOptions(accountTypeOptions); + fixedDepositProductToReturn.setCustomerSegmentOptions(customerSegmentOptions); + return fixedDepositProductToReturn; } + private Collection 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 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 }) diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java index 7b0f058d0..b4ab263b2 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsApiResourceSwagger.java @@ -342,6 +342,7 @@ final class FixedDepositProductsApiResourceSwagger { public GetFixedDepositProductsGlAccount interestOnSavingsAccount; public GetFixedDepositProductsGlAccount savingsControlAccount; public GetFixedDepositProductsGlAccount transfersInSuspenseAccount; + public GetFixedDepositProductsGlAccount escheatLiabilityAccount; public GetFixedDepositProductsGlAccount interestPayableAccount; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsBaseInterestRatesApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsBaseInterestRatesApiResource.java new file mode 100644 index 000000000..f740409c4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/FixedDepositProductsBaseInterestRatesApiResource.java @@ -0,0 +1,87 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.api; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializer; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import java.time.LocalDate; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.savings.DepositsApiConstants; +import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData; +import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService; +import org.springframework.stereotype.Component; + +@Path("/v1/fixeddepositproducts/baseinterestrates") +@Component +@RequiredArgsConstructor +public class FixedDepositProductsBaseInterestRatesApiResource { + + private final PlatformSecurityContext context; + private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService; + private final Gson gson = new GsonBuilder() + .registerTypeAdapter(LocalDate.class, + (JsonSerializer) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString())) + .create(); + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Get all active FD base interest rate charts", description = "Returns active CBS FD base interest-rate versions with slabs. Optionally filter by fdType (SINGLE, DOUBLE, SPECIAL).") + @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") }) + public String retrieveAllActiveInterestRates(@QueryParam("fdType") final String fdType) { + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME); + final List data = fdType != null && !fdType.isBlank() + ? this.fdBaseInterestRateReadPlatformService.retrieveActiveInterestRatesByType(fdType) + : this.fdBaseInterestRateReadPlatformService.retrieveAllActiveInterestRates(); + return gson.toJson(data); + } + + @GET + @Path("history") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Get all FD base interest rate charts", description = "Returns the full FD base chart history.") + @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") }) + public String retrieveHistory() { + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME); + final List data = this.fdBaseInterestRateReadPlatformService.retrieveAllInterestRates(); + return gson.toJson(data); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Save new FD base interest rate version", description = "Creates a new FD CBS base chart version for the given fdType and deactivates the previous active version.") + @ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") }) + public String saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) { + this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME); + final FdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, FdInterestRateSaveRequest.class); + final Long createdById = this.context.authenticatedUser().getId(); + final FdInterestRateEffectiveData saved = this.fdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById); + return gson.toJson(saved); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java index 25294dcb0..5027d4b3b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositAccountsApiResource.java @@ -45,6 +45,7 @@ import jakarta.ws.rs.core.UriInfo; import java.io.InputStream; import java.util.Arrays; import java.util.Collection; +import java.util.HashMap; import java.util.HashSet; import java.util.Set; import lombok.RequiredArgsConstructor; @@ -77,6 +78,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData; import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData; import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService; import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService; +import org.apache.fineract.portfolio.savings.service.RecurringDepositAccountInterestCalculationService; import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService; import org.glassfish.jersey.media.multipart.FormDataContentDisposition; import org.glassfish.jersey.media.multipart.FormDataParam; @@ -99,6 +101,7 @@ public class RecurringDepositAccountsApiResource { private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService; private final BulkImportWorkbookService bulkImportWorkbookService; private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService; + private final RecurringDepositAccountInterestCalculationService recurringDepositAccountInterestCalculationService; private final SqlValidator sqlValidator; @GET @@ -369,6 +372,18 @@ public class RecurringDepositAccountsApiResource { return this.toApiJsonSerializer.serialize(result); } + @POST + @Path("calculate-rd-interest") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String calculateRecurringDepositInterest(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson) { + JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson); + HashMap result = recurringDepositAccountInterestCalculationService + .calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper)); + + return toApiJsonSerializer.serializeResult(result); + } + private boolean is(final String commandParam, final String commandValue) { return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java index 92bfa6035..4b0e6d305 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResource.java @@ -333,7 +333,7 @@ public class RecurringDepositProductsApiResource { interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate, preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions); - recurringDepositProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "RD-IND-")); + // product code removed } recurringDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption); @@ -360,21 +360,6 @@ public class RecurringDepositProductsApiResource { .toList(); } - private String generateNextProductCode(final String tableName, final String prefix) { - try { - String maxCode = jdbcTemplate.queryForObject( - "SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 300", - String.class, prefix + "%"); - if (maxCode == null || maxCode.isBlank()) { - return prefix + "001"; - } - String numStr = maxCode.substring(prefix.length()); - int num = Integer.parseInt(numStr); - return prefix + String.format("%03d", num + 1); - } catch (Exception e) { - return prefix + "001"; - } - } // ── RD Interest Rate Chart endpoints ──────────────────────────────────── diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java index 4dc13ceb0..c505dc436 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/RecurringDepositProductsApiResourceSwagger.java @@ -360,6 +360,7 @@ final class RecurringDepositProductsApiResourceSwagger { public GetRecurringDepositProductsGlAccount interestOnSavingsAccount; public GetRecurringDepositProductsGlAccount savingsControlAccount; public GetRecurringDepositProductsGlAccount transfersInSuspenseAccount; + public GetRecurringDepositProductsGlAccount escheatLiabilityAccount; public GetRecurringDepositProductsGlAccount feeReceivableAccount; public GetRecurringDepositProductsGlAccount penaltyReceivableAccount; public GetRecurringDepositProductsGlAccount interestPayableAccount; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java index d018f4661..90b49c5f1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResource.java @@ -106,21 +106,6 @@ public class SavingsProductsApiResource { private final ConfigurationDomainService configurationDomainService; private final JdbcTemplate jdbcTemplate; - private String generateNextProductCode(final String tableName, final String prefix) { - try { - String maxCode = jdbcTemplate.queryForObject( - "SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?", - String.class, prefix + "%"); - if (maxCode == null || maxCode.isBlank()) { - return prefix + "001"; - } - String numStr = maxCode.substring(prefix.length()); - int num = Integer.parseInt(numStr); - return prefix + String.format("%03d", num + 1); - } catch (Exception e) { - return prefix + "001"; - } - } @POST @Consumes({ MediaType.APPLICATION_JSON }) @@ -315,7 +300,7 @@ public class SavingsProductsApiResource { periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions, accountMappingForPayment); - savingsProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "SB-IND-")); + // product code generation removed } return savingsProductToReturn; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java index 027d5ec7f..2432641d1 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsProductsApiResourceSwagger.java @@ -285,6 +285,7 @@ final class SavingsProductsApiResourceSwagger { public GetSavingsProductsGlAccount writeOffAccount; public GetSavingsProductsGlAccount savingsControlAccount; public GetSavingsProductsGlAccount transfersInSuspenseAccount; + public GetSavingsProductsGlAccount escheatLiabilityAccount; public GetSavingsProductsGlAccount interestPayableAccount; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java index baa27be3b..ba935bf0f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/DepositProductDataValidator.java @@ -632,8 +632,12 @@ public class DepositProductDataValidator { } private void validateDepositAmountForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) { - final BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element); - baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount(); + final BigDecimal depositAmount = this.fromApiJsonHelper.parameterExists(depositAmountParamName, element) + ? this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element) + : null; + if (depositAmount != null) { + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount(); + } BigDecimal depositMinAmount = null; if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) { @@ -667,7 +671,7 @@ public class DepositProductDataValidator { if (this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)) { depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element); - baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount(); + baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount(); } BigDecimal depositMinAmount = null; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateEffectiveData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateEffectiveData.java new file mode 100644 index 000000000..ecef7a745 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateEffectiveData.java @@ -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 + */ +package org.apache.fineract.portfolio.savings.data; + +import java.time.LocalDate; +import java.util.List; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FdInterestRateEffectiveData { + + private Long id; + private String fdType; + private String versionLabel; + private LocalDate effectiveFromDate; + private LocalDate effectiveToDate; + private Boolean isActive; + private String remarks; + private List slabs; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateSlabData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateSlabData.java new file mode 100644 index 000000000..e11e414f4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/FdInterestRateSlabData.java @@ -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 + */ +package org.apache.fineract.portfolio.savings.data; + +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class FdInterestRateSlabData { + + private Long id; + private Integer srNo; + private String description; + private Integer periodTypeEnum; + private String periodTypeLabel; + private Integer fromPeriod; + private Integer toPeriod; + private BigDecimal annualInterestRate; + private BigDecimal seniorCitizenRate; +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java index 3342eeec4..7055c5d6d 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositAccountAssembler.java @@ -120,6 +120,7 @@ public class DepositAccountAssembler { private final PaymentDetailAssembler paymentDetailAssembler; private final ExternalIdFactory externalIdFactory; + private final SavingsAccountAssembler savingsAccountAssembler; @Autowired public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper, @@ -130,7 +131,8 @@ public class DepositAccountAssembler { final DepositProductAssembler depositProductAssembler, final RecurringDepositProductRepository recurringDepositProductRepository, final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context, - final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory) { + final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory, + final SavingsAccountAssembler savingsAccountAssembler) { this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper; this.clientRepository = clientRepository; @@ -146,6 +148,7 @@ public class DepositAccountAssembler { this.context = context; this.paymentDetailAssembler = paymentDetailAssembler; this.externalIdFactory = externalIdFactory; + this.savingsAccountAssembler = savingsAccountAssembler; } /** @@ -358,6 +361,9 @@ public class DepositAccountAssembler { if (account != null) { account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper); account.validateNewApplicationState(depositAccountType.resourceName()); + // Apply CBS-specific fields (accountStatus, customerSegment, autoDebitFacility, + // linkedAccount, nominee, jointHolders, guardian, etc.) + this.savingsAccountAssembler.applyCbsFields(command, element, account); } return account; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java new file mode 100644 index 000000000..688b29e18 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffective.java @@ -0,0 +1,75 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "m_fd_base_interest_rate_effective") +@Getter +@NoArgsConstructor +public class FdBaseInterestRateEffective { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "fd_type", length = 20) + private String fdType; + + @Column(name = "version_label") + private String versionLabel; + + @Column(name = "effective_from_date") + private LocalDate effectiveFromDate; + + @Column(name = "effective_to_date") + private LocalDate effectiveToDate; + + @Column(name = "is_active") + private Boolean isActive; + + @Column(name = "remarks") + private String remarks; + + @Column(name = "created_by_id") + private Long createdById; + + @Column(name = "created_date") + private LocalDate createdDate; + + public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks, + Long createdById) { + FdBaseInterestRateEffective e = new FdBaseInterestRateEffective(); + e.fdType = fdType; + e.versionLabel = versionLabel; + e.effectiveFromDate = effectiveFromDate; + e.isActive = true; + e.remarks = remarks; + e.createdById = createdById; + e.createdDate = LocalDate.now(); + return e; + } + + public void deactivate(LocalDate toDate) { + this.isActive = false; + this.effectiveToDate = toDate; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffectiveRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffectiveRepository.java new file mode 100644 index 000000000..138aa4428 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateEffectiveRepository.java @@ -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 + */ +package org.apache.fineract.portfolio.savings.domain; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FdBaseInterestRateEffectiveRepository extends JpaRepository { + + List findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(String fdType); + + List findByFdTypeOrderByEffectiveFromDateDesc(String fdType); + + List findByIsActiveTrueOrderByEffectiveFromDateDesc(); + + List findByOrderByEffectiveFromDateDesc(); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java new file mode 100644 index 000000000..6a6974660 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistory.java @@ -0,0 +1,76 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.math.BigDecimal; +import java.time.LocalDate; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Table(name = "m_fd_base_interest_rate_slab_history") +@Getter +@NoArgsConstructor +public class FdBaseInterestRateSlabHistory { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "effective_id") + private Long effectiveId; + + @Column(name = "sr_no") + private Integer srNo; + + @Column(name = "description") + private String description; + + @Column(name = "period_type_enum") + private Integer periodTypeEnum; + + @Column(name = "from_period") + private Integer fromPeriod; + + @Column(name = "to_period") + private Integer toPeriod; + + @Column(name = "annual_interest_rate") + private BigDecimal annualInterestRate; + + @Column(name = "senior_citizen_rate") + private BigDecimal seniorCitizenRate; + + @Column(name = "created_date") + private LocalDate createdDate; + + public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum, + Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) { + FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory(); + s.effectiveId = effectiveId; + s.srNo = srNo; + s.description = description; + s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1; + s.fromPeriod = fromPeriod; + s.toPeriod = toPeriod; + s.annualInterestRate = annualInterestRate; + s.seniorCitizenRate = seniorCitizenRate; + s.createdDate = LocalDate.now(); + return s; + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistoryRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistoryRepository.java new file mode 100644 index 000000000..733fb3891 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FdBaseInterestRateSlabHistoryRepository.java @@ -0,0 +1,20 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.domain; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface FdBaseInterestRateSlabHistoryRepository extends JpaRepository { + + List findByEffectiveIdOrderBySrNo(Long effectiveId); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java index cdeccec21..2d72f6d59 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountAssembler.java @@ -40,6 +40,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDat import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nomineeParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.specialCategoryParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.weakerSectionParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName; +import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName; import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName; @@ -384,6 +386,14 @@ public class SavingsAccountAssembler { account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element)); account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element)); + + if (command.parameterExists(nominationFacilityParamName)) { + account.setNominationFacility(this.fromApiJsonHelper.extractBooleanNamed(nominationFacilityParamName, element)); + } + if (command.parameterExists(autoDebitFacilityParamName)) { + account.setAutoDebitFacility(this.fromApiJsonHelper.extractBooleanNamed(autoDebitFacilityParamName, element)); + } + account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element)); account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element)); account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element)); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java index f935264d3..fbfde7e34 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountReadPlatformServiceImpl.java @@ -408,6 +408,24 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions); template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart); + + try { + final Collection accountTypeOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE); + final Collection customerSegmentOpts = this.codeValueReadPlatformService + .retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT); + ((FixedDepositAccountData) template).setAccountTypeOptions(accountTypeOpts); + ((FixedDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts); + } catch (final Exception ex) { + log.warn("Failed to load FD account type/customer segment options: {}", ex.getMessage()); + } + + try { + final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId); + template.setPreviewAccountNo(preview); + } catch (final Exception ex) { + log.warn("Failed to generate preview account number for FD productId={}: {}", productId, ex.getMessage()); + } } else if (depositAccountType.isRecurringDeposit()) { // Save CBS fields from mapper result before factory methods create new objects final RecurringDepositAccountData rdTemplateWithCbs = (RecurringDepositAccountData) template; @@ -696,6 +714,16 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, "); selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,"); selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, "); + selectFieldsSqlBuilder.append("sa.cbs_account_type as accountType, sa.customer_segment as customerSegment, "); + selectFieldsSqlBuilder.append("sa.account_status as accountStatus, sa.mode_of_operation as modeOfOperation, "); + selectFieldsSqlBuilder.append("sa.special_category as specialCategory, sa.weaker_section as weakerSection, "); + selectFieldsSqlBuilder.append("sa.guardian_name as guardianName, sa.guardian_cif_number as guardianCifNumber, "); + selectFieldsSqlBuilder.append("sa.guardian_relationship as guardianRelationship, sa.guardian_client_id as guardianClientId, "); + selectFieldsSqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, "); + selectFieldsSqlBuilder.append("sa.introducer_client_id as introducerClientId, sa.introducer_name as introducerName, "); + selectFieldsSqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, "); + selectFieldsSqlBuilder.append("sa.linked_account_id as linkedAccountId, sa.linked_account_type as linkedAccountType, "); + selectFieldsSqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, "); selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,"); selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName "); @@ -706,6 +734,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectTablesSqlBuilder.append("from m_savings_account sa "); selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id "); selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id "); + selectTablesSqlBuilder.append("left join m_code_value icv on icv.id = sp.interest_calculate_selector "); selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code "); selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id "); selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id "); @@ -714,7 +743,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid "); selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid "); selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid "); - selectTablesSqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid "); + selectTablesSqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid "); selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid "); selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id "); @@ -904,7 +933,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings, "); - sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId "); + sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId, "); + sqlBuilder.append("sp.fd_type as fdType, sp.product_code as productCode, sp.effective_date as effectiveDate, "); + sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); + sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); + sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sp.last_interest_date as lastInterestDate, 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("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); + sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); + sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); + sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); sqlBuilder.append(super.selectTablesSql()); @@ -954,10 +1004,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final Long transferToSavingsId = JdbcSupport.getLong(rs, TRANSFER_TO_SAVINGS_ID); - return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, + final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId); + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), + JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + return result; } } @@ -1014,7 +1080,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead sqlBuilder.append("datp.maturity_date as maturityDate, "); sqlBuilder.append("datp.deposit_period as depositPeriod, "); sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, "); - sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId "); + sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, "); + sqlBuilder.append("sp.product_code as productCode, sp.effective_date as effectiveDate, "); + sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, "); + sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, "); + sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sp.last_interest_date as lastInterestDate, 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("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); + sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, "); + sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, "); + sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate "); sqlBuilder.append(this.selectTablesSql()); sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id "); @@ -1071,12 +1158,30 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead : SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId); final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, EXPECTED_FIRST_DEPOSIT_ON_DATE); - return RecurringDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, - preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, - depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate, - totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments, - isCalendarInherited); + final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData, + preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, + minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, + maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, + onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, + allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited); + + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"), + JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + + return result; } } @@ -1423,6 +1528,31 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, "); sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, "); sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, "); + sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, sa.fd_type as fdType, "); + sqlBuilder.append("sa.product_code as productCode, "); + sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, "); + sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, "); + sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, "); + sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sa.days_to_inactive as daysToInactive, sa.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sa.days_to_escheat as daysToEscheat, "); + sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("icv.code_value as interestCalculationTypeName, "); + sqlBuilder.append("sa.average_period as averagePeriod, "); + sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, "); + sqlBuilder.append("sa.balance_frequency as balanceFrequency, "); + sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, "); + sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, "); + sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, "); + sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, "); + sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, "); + sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, "); + sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, "); sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, "); sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, "); sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, "); @@ -1431,6 +1561,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId "); sqlBuilder.append(super.selectTablesSql()); + sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector "); this.schemaSql = sqlBuilder.toString(); } @@ -1472,10 +1603,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead final EnumOptionData onAccountClosureType = null; final Boolean transferInterestToSavings = false; - return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest, - preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod, - depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null); + final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, + preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, + maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null); + result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"), + JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"), + JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"), + rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"), + JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"), + JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"), + rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"), + JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"), + rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"), + rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"), + rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"), + rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"), + rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"), + rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"), + rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate")); + return result; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java index 744b41b7a..0a277f7ff 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl.java @@ -172,6 +172,9 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth); + + this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account); + this.fixedDepositAccountRepository.saveAndFlush(account); if (account.isAccountNumberRequiresAutoGeneration()) { @@ -350,6 +353,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl DepositAccountType.FIXED_DEPOSIT); checkClientOrGroupActive(account); account.modifyApplication(command, changes); + this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account); account.validateNewApplicationState(DepositAccountType.FIXED_DEPOSIT.resourceName()); if (!changes.isEmpty()) { @@ -440,6 +444,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl DepositAccountType.RECURRING_DEPOSIT); checkClientOrGroupActive(account); account.modifyApplication(command, changes); + this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account); account.validateNewApplicationState(DepositAccountType.RECURRING_DEPOSIT.resourceName()); if (!changes.isEmpty()) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java index 93571d776..9a2cc2852 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositProductReadPlatformServiceImpl.java @@ -230,11 +230,37 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, "); sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, "); sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, dptp.deposit_amount as depositAmount, "); - sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount "); + sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, "); + sqlBuilder.append("sp.account_type as accountType, sp.fd_type as fdType, sp.product_code as productCode, "); + sqlBuilder.append("sp.customer_segment as customerSegment, "); + sqlBuilder.append("sp.gl_account_id as glAccountId, gla.name as glAccountName, "); + 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.is_dormancy_tracking_active as isDormancyTrackingActive, "); + sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, "); + sqlBuilder.append("sp.days_to_escheat as daysToEscheat, "); + sqlBuilder.append("sp.interest_calculate_selector as interestCalculateSelector, "); + sqlBuilder.append("sp.average_period as averagePeriod, "); + sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, "); + sqlBuilder.append("sp.last_interest_date as lastInterestDate, 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("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, "); + sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, "); + sqlBuilder.append("sp.lien_marked as lienMarked, sp.maturity_instructions as maturityInstructions, "); + sqlBuilder.append("sp.credit_maturity_proceeds as creditMaturityProceeds, "); + sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, "); + sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, "); + sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, "); + sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, "); + sqlBuilder.append("sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, "); + sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers "); sqlBuilder.append("from m_savings_product sp "); sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id "); 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 "); + sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id "); + sqlBuilder.append("left join acc_gl_account gla on gla.id = sp.gl_account_id "); this.schemaSql = sqlBuilder.toString(); } @@ -271,9 +297,55 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount"); final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount"); - return FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable, preClosurePenalInterest, - preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType, - inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, maxDepositAmount); + final FixedDepositProductData result = FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable, + preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, + maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, + maxDepositAmount); + + // CBS Custom Fields + result.setAccountType(rs.getString("accountType")); + result.setFdType(rs.getString("fdType")); + result.setProductCode(rs.getString("productCode")); + result.setCustomerSegment(rs.getString("customerSegment")); + result.setGlAccountId(JdbcSupport.getLong(rs, "glAccountId")); + result.setGlAccountName(rs.getString("glAccountName")); + final java.time.LocalDate effectiveDate = JdbcSupport.getLocalDate(rs, "effectiveDate"); + result.setEffectiveDate(effectiveDate != null ? effectiveDate.toString() : null); + final java.time.LocalDate effectiveTo = JdbcSupport.getLocalDate(rs, "effectiveTo"); + result.setEffectiveTo(effectiveTo != null ? effectiveTo.toString() : null); + final java.time.LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate"); + result.setEndDate(endDate != null ? endDate.toString() : null); + result.setIntroducerRequired(rs.getBoolean("introducerRequired")); + result.setShareLinkingEnabled(rs.getBoolean("shareLinkingEnabled")); + result.setNominationFacility(rs.getBoolean("nominationFacility")); + result.setDormancyAlert(rs.getBoolean("dormancyAlert")); + result.setIsDormancyTrackingActive(rs.getBoolean("isDormancyTrackingActive")); + result.setDaysToInactive(JdbcSupport.getLong(rs, "daysToInactive")); + result.setDaysToDormancy(JdbcSupport.getLong(rs, "daysToDormancy")); + result.setDaysToEscheat(JdbcSupport.getLong(rs, "daysToEscheat")); + result.setInterestCalculateSelector(JdbcSupport.getInteger(rs, "interestCalculateSelector")); + result.setAveragePeriod(rs.getString("averagePeriod")); + result.setAutoPost(rs.getBoolean("autoPost")); + result.setFrequencyFixDay(JdbcSupport.getInteger(rs, "frequencyFixDay")); + final java.time.LocalDate lastInterestDate = JdbcSupport.getLocalDate(rs, "lastInterestDate"); + result.setLastInterestDate(lastInterestDate != null ? lastInterestDate.toString() : null); + result.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod")); + result.setBalanceFrequency(rs.getString("balanceFrequency")); + result.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing")); + result.setAutoDebitFacility(rs.getBoolean("autoDebitFacility")); + result.setLoanFacility(rs.getBoolean("loanFacility")); + result.setPrematureWithdrawalOption(rs.getBoolean("prematureWithdrawalOption")); + result.setTenureFlexibility(rs.getBoolean("tenureFlexibility")); + result.setLienMarked(rs.getBoolean("lienMarked")); + result.setMaturityInstructions(rs.getBoolean("maturityInstructions")); + result.setCreditMaturityProceeds(rs.getBoolean("creditMaturityProceeds")); + result.setPenaltyGracePeriodMissedInstallments(rs.getBoolean("penaltyGracePeriodMissedInstallments")); + result.setPanelRequestApplicable(rs.getBoolean("panelRequestApplicable")); + result.setNoPenaltyNomineeHeirClosure(rs.getBoolean("noPenaltyNomineeHeirClosure")); + result.setDeceasedSettlement(rs.getBoolean("deceasedSettlement")); + result.setAllowDebitTxnAfterDueDate(rs.getBoolean("allowDebitTxnAfterDueDate")); + result.setWithdrawalFeeForTransfers(rs.getBoolean("withdrawalFeeForTransfers")); + return result; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformService.java new file mode 100644 index 000000000..cef8774f4 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformService.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.apache.fineract.portfolio.savings.service; + +import java.util.List; +import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest; +import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData; + +public interface FdBaseInterestRateReadPlatformService { + + List retrieveAllInterestRates(); + + List retrieveAllActiveInterestRates(); + + List retrieveActiveInterestRatesByType(String fdType); + + FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformServiceImpl.java new file mode 100644 index 000000000..baf53d972 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/FdBaseInterestRateReadPlatformServiceImpl.java @@ -0,0 +1,128 @@ +/** + * 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 + */ +package org.apache.fineract.portfolio.savings.service; + +import java.time.LocalDate; +import java.util.List; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest; +import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData; +import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffective; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistory; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +public class FdBaseInterestRateReadPlatformServiceImpl implements FdBaseInterestRateReadPlatformService { + + private final FdBaseInterestRateEffectiveRepository effectiveRepository; + private final FdBaseInterestRateSlabHistoryRepository slabRepository; + + @Override + public List retrieveAllInterestRates() { + return effectiveRepository.findByOrderByEffectiveFromDateDesc().stream().map(this::toData).collect(Collectors.toList()); + } + + @Override + public List retrieveAllActiveInterestRates() { + return effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc().stream().map(this::toData) + .collect(Collectors.toList()); + } + + @Override + public List retrieveActiveInterestRatesByType(String fdType) { + return effectiveRepository.findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType).stream().map(this::toData) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById) { + final String fdType = request.getFdType(); + final LocalDate today = LocalDate.now(); + + final List activeVersions = effectiveRepository + .findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType); + for (FdBaseInterestRateEffective version : activeVersions) { + version.deactivate(today); + } + if (!activeVersions.isEmpty()) { + effectiveRepository.saveAll(activeVersions); + } + + final String label = request.getVersionLabel() != null && !request.getVersionLabel().isBlank() + ? request.getVersionLabel() + : "FD " + fdType + " Rate Version " + LocalDate.now(); + + FdBaseInterestRateEffective newVersion = FdBaseInterestRateEffective.create(fdType, label, today, request.getRemarks(), createdById); + newVersion = effectiveRepository.saveAndFlush(newVersion); + + final Long versionId = newVersion.getId(); + int srCounter = 1; + if (request.getSlabs() != null) { + for (FdInterestRateSaveRequest.SlabRequest slabRequest : request.getSlabs()) { + final int sr = slabRequest.getSrNo() != null ? slabRequest.getSrNo() : srCounter++; + final FdBaseInterestRateSlabHistory slab = FdBaseInterestRateSlabHistory.create(versionId, sr, + slabRequest.getDescription(), slabRequest.getPeriodTypeEnum(), slabRequest.getFromPeriod(), + slabRequest.getToPeriod(), slabRequest.getAnnualInterestRate(), slabRequest.getSeniorCitizenRate()); + slabRepository.save(slab); + } + } + + return toData(newVersion); + } + + private FdInterestRateEffectiveData toData(FdBaseInterestRateEffective e) { + List slabs = slabRepository.findByEffectiveIdOrderBySrNo(e.getId()).stream() + .map(s -> FdInterestRateSlabData.builder() + .id(s.getId()) + .srNo(s.getSrNo()) + .description(s.getDescription()) + .periodTypeEnum(s.getPeriodTypeEnum()) + .periodTypeLabel(periodLabel(s.getPeriodTypeEnum())) + .fromPeriod(s.getFromPeriod()) + .toPeriod(s.getToPeriod()) + .annualInterestRate(s.getAnnualInterestRate()) + .seniorCitizenRate(s.getSeniorCitizenRate()) + .build()) + .collect(Collectors.toList()); + + return FdInterestRateEffectiveData.builder() + .id(e.getId()) + .fdType(e.getFdType()) + .versionLabel(e.getVersionLabel()) + .effectiveFromDate(e.getEffectiveFromDate()) + .effectiveToDate(e.getEffectiveToDate()) + .isActive(e.getIsActive()) + .remarks(e.getRemarks()) + .slabs(slabs) + .build(); + } + + private String periodLabel(Integer code) { + if (code == null) { + return "Days"; + } + + return switch (code) { + case 0 -> "Days"; + case 1 -> "Weeks"; + case 2 -> "Months"; + case 3 -> "Years"; + default -> "Days"; + }; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationService.java new file mode 100644 index 000000000..8c965e9c0 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationService.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.portfolio.savings.service; + +import java.util.HashMap; +import org.apache.fineract.infrastructure.core.api.JsonQuery; + +public interface RecurringDepositAccountInterestCalculationService { + + HashMap calculateInterest(JsonQuery query); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationServiceImpl.java new file mode 100644 index 000000000..f9379f53a --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/RecurringDepositAccountInterestCalculationServiceImpl.java @@ -0,0 +1,138 @@ +/** + * 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.savings.service; + +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName; +import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName; + +import com.google.gson.JsonElement; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.time.LocalDate; +import java.util.HashMap; +import lombok.RequiredArgsConstructor; +import org.apache.fineract.infrastructure.core.api.JsonQuery; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.organisation.monetary.domain.MoneyHelper; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RecurringDepositAccountInterestCalculationServiceImpl implements RecurringDepositAccountInterestCalculationService { + + private final FromJsonHelper fromApiJsonHelper; + + @Override + public HashMap calculateInterest(JsonQuery query) { + JsonElement element = query.parsedJson(); + BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element); + BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName, element); + Long tenureInMonths = this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element); + Long interestCompoundingPeriodInMonths = this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName, + element); + LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element); + if (submittedOnDate == null) { + submittedOnDate = LocalDate.now(); + } + + BigDecimal maturityAmount = this.calculateInterestInternal(depositAmount, annualInterestRate, tenureInMonths, + interestCompoundingPeriodInMonths, submittedOnDate); + + BigDecimal totalPrincipal = depositAmount.multiply(BigDecimal.valueOf(tenureInMonths)); + BigDecimal totalInterest = maturityAmount.subtract(totalPrincipal); + + String warning = "This is an approximate calculated amount - it may vary slightly when the account is created"; + + HashMap result = new HashMap<>(); + result.put("maturityAmount", maturityAmount); + result.put("totalPrincipal", totalPrincipal); + result.put("totalInterest", totalInterest); + result.put("daysInYear", 365); // Standard for this product as per image + result.put("warning", warning); + + return result; + } + + public BigDecimal calculateInterestInternal(BigDecimal installmentAmount, BigDecimal annualInterestRate, Long tenureInMonths, + Long interestCompoundingPeriodInMonths, LocalDate submittedOnDate) { + MathContext mc = MoneyHelper.getMathContext(); + BigDecimal maturityAmount = BigDecimal.ZERO; + int totalMonths = Math.toIntExact(tenureInMonths); + + if (interestCompoundingPeriodInMonths == 0) { + // Standard Banking Approach for Daily Compounding RD using Actual Calendar Days + BigDecimal currentBalance = BigDecimal.ZERO; + double dailyRate = annualInterestRate.doubleValue() / 36500.0; + LocalDate date = submittedOnDate; + + for (int m = 1; m <= totalMonths; m++) { + // Add monthly installment at the start of the month + currentBalance = currentBalance.add(installmentAmount); + + // Get actual days in the current month + int daysInMonth = date.lengthOfMonth(); + + // Calculate interest for the month using daily compounding + double multiplier = Math.pow(1.0 + dailyRate, daysInMonth); + currentBalance = currentBalance.multiply(BigDecimal.valueOf(multiplier), mc); + + // Move to next month + date = date.plusMonths(1); + } + maturityAmount = currentBalance; + } else { + // Periodic Compounding (Monthly, Quarterly, etc.) + BigDecimal monthlyRate = annualInterestRate.divide(BigDecimal.valueOf(1200), mc); + BigDecimal i = monthlyRate; + int n = totalMonths; + + // Standard RD formula for monthly compounding: + // M = P * ((1+i)^n - 1) / i * (1+i) + // For other periods, we can approximate or use a loop. + // Using loop for consistency with various compounding periods. + BigDecimal compoundingFrequencyMonths = BigDecimal.valueOf(interestCompoundingPeriodInMonths); + BigDecimal ratePerCompounding = annualInterestRate.divide(BigDecimal.valueOf(100), mc) + .multiply(compoundingFrequencyMonths).divide(BigDecimal.valueOf(12), mc); + + for (int m = 1; m <= totalMonths; m++) { + BigDecimal monthsRemaining = BigDecimal.valueOf(totalMonths - m + 1); + BigDecimal numberOfCompoundings = monthsRemaining.divide(compoundingFrequencyMonths, mc); + + BigDecimal installmentMaturity = installmentAmount.multiply( + BigDecimal.ONE.add(ratePerCompounding).pow(numberOfCompoundings.intValue(), mc), mc); + + // Add remaining partial compounding period interest (simple interest for the rest) + BigDecimal remainingMonths = monthsRemaining.remainder(compoundingFrequencyMonths); + if (remainingMonths.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal partialInterest = installmentMaturity.multiply(annualInterestRate).multiply(remainingMonths) + .divide(BigDecimal.valueOf(1200), mc); + installmentMaturity = installmentMaturity.add(partialInterest); + } + + maturityAmount = maturityAmount.add(installmentMaturity); + } + } + + return maturityAmount.setScale(2, RoundingMode.HALF_UP); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java index 231e19a04..3080f530a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountReadPlatformServiceImpl.java @@ -800,7 +800,8 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead sqlBuilder.append("sa.linked_account_no as linkedAccountNo, "); sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, "); sqlBuilder.append("sp.msme as msme, "); - sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, "); + sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, "); + sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, "); // Facility config fields sqlBuilder.append("safc.atm_facility as atmFacility, "); sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, "); @@ -1054,6 +1055,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead accountData.setLinkedAccountNo(rs.getString("linkedAccountNo")); accountData.setBalanceSwapOption(rs.getObject("balanceSwapOption") != null ? rs.getBoolean("balanceSwapOption") : null); accountData.setMsme(rs.getObject("msme") != null ? rs.getBoolean("msme") : null); + accountData.setNominationFacility(rs.getObject("nominationFacility") != null ? rs.getBoolean("nominationFacility") : null); accountData.setAutoDebitFacility(rs.getObject("autoDebitFacility") != null ? rs.getBoolean("autoDebitFacility") : null); // Nominee as parsed map diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index f30712b50..dfb6e2d53 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -1391,8 +1391,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi public SavingsAccountData fallbackPostInterest(SavingsAccountData savingsAccountData, boolean postInterestAs, LocalDate transactionDate, boolean backdatedTxnsAllowedTill, Throwable t) { // NOTE: allow caller to catch the exceptions - // NOTE: wrap throwable only if really necessary - throw errorHandler.getMappable(t, null, null, "savings.postinterest"); + throw org.apache.fineract.infrastructure.core.exception.ErrorHandler.getMappable(t, null, null, "savings.postinterest"); } @Transactional diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java index 1f92a0e92..4ea891567 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/starter/SavingsConfiguration.java @@ -146,8 +146,12 @@ import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatform import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformServiceJpaRepositoryImpl; import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository; import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository; +import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository; import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformService; import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformServiceImpl; +import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService; +import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformServiceImpl; import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster; import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask; import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService; @@ -171,6 +175,14 @@ public class SavingsConfiguration { return new RdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository); } + @Bean + @ConditionalOnMissingBean(FdBaseInterestRateReadPlatformService.class) + public FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService( + FdBaseInterestRateEffectiveRepository effectiveRepository, + FdBaseInterestRateSlabHistoryRepository slabRepository) { + return new FdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository); + } + @Bean @ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class) public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context, diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index b34aedf38..3b28d2553 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -290,4 +290,8 @@ + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0279_add_fd_type_and_base_interest_rate_tables.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0279_add_fd_type_and_base_interest_rate_tables.xml new file mode 100644 index 000000000..417d8a909 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0279_add_fd_type_and_base_interest_rate_tables.xml @@ -0,0 +1,300 @@ + + + + + + ANY + + + + + + + + + + + + + ANY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ANY + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SINGLE' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'DOUBLE' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' + + + + + + + + + + + + + + ANY + + SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SPECIAL' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0280_add_fixed_deposit_product_codes.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0280_add_fixed_deposit_product_codes.xml new file mode 100644 index 000000000..a54ddf3c5 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0280_add_fixed_deposit_product_codes.xml @@ -0,0 +1,53 @@ + + + + + + ANY + + INSERT INTO m_code (code_name, is_system_defined) + SELECT 'FixedDepositProductAccountType', false + WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductAccountType'); + + + INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory) + SELECT c.id, v.code_value, v.order_position, true, false + FROM m_code c, + (VALUES + ('Fd Account', 1) + ) AS v(code_value, order_position) + WHERE c.code_name = 'FixedDepositProductAccountType' + AND NOT EXISTS ( + SELECT 1 FROM m_code_value cv + WHERE cv.code_id = c.id AND cv.code_value = v.code_value + ); + + + + + + ANY + + INSERT INTO m_code (code_name, is_system_defined) + SELECT 'FixedDepositProductCustomerSegment', false + WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductCustomerSegment'); + + + INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory) + SELECT c.id, v.code_value, v.order_position, true, false + FROM m_code c, + (VALUES + ('Individual', 1), + ('Non-Individual', 2) + ) AS v(code_value, order_position) + WHERE c.code_name = 'FixedDepositProductCustomerSegment' + AND NOT EXISTS ( + SELECT 1 FROM m_code_value cv + WHERE cv.code_id = c.id AND cv.code_value = v.code_value + ); + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0281_update_fd_base_interest_rate_seed_data.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0281_update_fd_base_interest_rate_seed_data.xml new file mode 100644 index 000000000..411069f31 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0281_update_fd_base_interest_rate_seed_data.xml @@ -0,0 +1,105 @@ + + + + + ANY + + + + + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '1 to 29 Days', + from_period = 1, + to_period = 29 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1') + AND sr_no = 1; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '30 to 89 Days', + from_period = 30, + to_period = 89 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1') + AND sr_no = 2; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '90 to 179 Days', + from_period = 90, + to_period = 179 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1') + AND sr_no = 3; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '180+ Days', + from_period = 180, + to_period = NULL + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1') + AND sr_no = 4; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '1 to 2 Years', + from_period = 1, + to_period = 2 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1') + AND sr_no = 1; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '3 to 5 Years', + from_period = 3, + to_period = 5 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1') + AND sr_no = 2; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '6 to 10 Years', + from_period = 6, + to_period = 10 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1') + AND sr_no = 3; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '11+ Years', + from_period = 11, + to_period = NULL + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1') + AND sr_no = 4; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '1 to 12 Months', + from_period = 1, + to_period = 12 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1') + AND sr_no = 1; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '13 to 24 Months', + from_period = 13, + to_period = 24 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1') + AND sr_no = 2; + + UPDATE m_fd_base_interest_rate_slab_history + SET description = '25 to 60 Months', + from_period = 25, + to_period = 60 + WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1') + AND sr_no = 3; + + INSERT INTO m_fd_base_interest_rate_slab_history + (effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, senior_citizen_rate, created_date) + SELECT e.id, 4, '61+ Months', 2, 61, NULL, 7.250000, 7.750000, CURRENT_DATE + FROM m_fd_base_interest_rate_effective e + WHERE e.fd_type = 'SPECIAL' AND e.version_label = 'FD-SPECIAL-V1' + AND NOT EXISTS ( + SELECT 1 + FROM m_fd_base_interest_rate_slab_history sh + WHERE sh.effective_id = e.id + AND sh.sr_no = 4 + ); + + + + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0284_remove_product_code_unique_constraints.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0284_remove_product_code_unique_constraints.xml new file mode 100644 index 000000000..861e41a17 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0284_remove_product_code_unique_constraints.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + product_code = '' OR product_code IS NULL + + + + + product_code = '' OR product_code IS NULL + + + + diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/AccountNumberGeneratorTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/AccountNumberGeneratorTest.java index 723e6c7ad..b7032cf7b 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/AccountNumberGeneratorTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/account/jobs/executestandinginstructions/AccountNumberGeneratorTest.java @@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount; import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.springframework.jdbc.core.JdbcTemplate; public class AccountNumberGeneratorTest { @@ -54,6 +55,7 @@ public class AccountNumberGeneratorTest { private SavingsAccountRepository savingsRepo; private GLAccountRepository glAccountRepo; private SavingsProductRepository savingsProductRepo; + private JdbcTemplate jdbcTemplate; private AccountNumberGenerator generator; @@ -65,8 +67,9 @@ public class AccountNumberGeneratorTest { savingsRepo = mock(SavingsAccountRepository.class); glAccountRepo = mock(GLAccountRepository.class); savingsProductRepo = mock(SavingsProductRepository.class); + jdbcTemplate = mock(JdbcTemplate.class); - generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo); + generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo, jdbcTemplate); GlobalConfigurationPropertyData accountLengthConfig = mock(GlobalConfigurationPropertyData.class); when(accountLengthConfig.getValue()).thenReturn(Long.valueOf("9")); diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java index ec8051265..2b26615c3 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositAccountData.java @@ -24,6 +24,7 @@ import java.util.Collection; import lombok.Getter; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; +import org.apache.fineract.infrastructure.codes.data.CodeValueData; import org.apache.fineract.infrastructure.core.data.EnumOptionData; import org.apache.fineract.organisation.monetary.data.CurrencyData; import org.apache.fineract.organisation.staff.data.StaffData; @@ -73,6 +74,48 @@ public final class FixedDepositAccountData extends DepositAccountData { private Collection paymentTypeOptions; private final Collection maturityInstructionOptions; + // CBS product/account fields used by the FD UI + private String accountType; + private String customerSegment; + private String fdType; + private Collection accountTypeOptions; + private Collection customerSegmentOptions; + private String productCode; + private LocalDate effectiveDate; + private LocalDate effectiveTo; + private LocalDate endDate; + private Boolean introducerRequired; + private Boolean shareLinkingEnabled; + private Boolean nominationFacility; + private Boolean dormancyAlert; + private Boolean isDormancyTrackingActive; + private Long daysToInactive; + private Long daysToDormancy; + private Long daysToEscheat; + private Integer interestCalculateSelector; + private String interestCalculationTypeName; + private String averagePeriod; + private Boolean autoPost; + private Integer frequencyFixDay; + private LocalDate lastInterestDate; + private Boolean enableLockinPeriod; + private String balanceFrequency; + private Boolean accountClosedZeroBalanceInDayClosing; + private Boolean balanceSwapOption; + private Boolean msme; + private Boolean autoDebitFacility; + private Boolean loanFacility; + private Boolean prematureWithdrawalOption; + private Boolean tenureFlexibility; + private Boolean lienMarked; + private Boolean maturityInstructions; + private Boolean creditMaturityProceeds; + private Boolean penaltyGracePeriodMissedInstallments; + private Boolean panelRequestApplicable; + private Boolean noPenaltyNomineeHeirClosure; + private Boolean deceasedSettlement; + private Boolean allowDebitTxnAfterDueDate; + // import fields private transient Integer rowIndex; private String dateFormat; @@ -92,6 +135,123 @@ public final class FixedDepositAccountData extends DepositAccountData { locale, dateFormat); } + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public void setCustomerSegment(String customerSegment) { + this.customerSegment = customerSegment; + } + + public void setAccountTypeOptions(Collection accountTypeOptions) { + this.accountTypeOptions = accountTypeOptions; + } + + public void setCustomerSegmentOptions(Collection customerSegmentOptions) { + this.customerSegmentOptions = customerSegmentOptions; + } + + public void setFdType(String fdType) { this.fdType = fdType; } + public String getFdType() { return fdType; } + + public void setCbsProductFields(final String accountType, final String customerSegment, final String fdType, final String productCode, + final LocalDate effectiveDate, final LocalDate effectiveTo, final LocalDate endDate, final Boolean introducerRequired, + final Boolean shareLinkingEnabled, final Boolean nominationFacility, final Boolean dormancyAlert, + final Boolean isDormancyTrackingActive, final Long daysToInactive, final Long daysToDormancy, final Long daysToEscheat, + final Integer interestCalculateSelector, final String interestCalculationTypeName, final String averagePeriod, + final Boolean autoPost, final Integer frequencyFixDay, final LocalDate lastInterestDate, final Boolean enableLockinPeriod, + final String balanceFrequency, final Boolean accountClosedZeroBalanceInDayClosing, final Boolean balanceSwapOption, + final Boolean msme, final Boolean autoDebitFacility, final Boolean loanFacility, final Boolean prematureWithdrawalOption, + final Boolean tenureFlexibility, final Boolean lienMarked, final Boolean maturityInstructions, + final Boolean creditMaturityProceeds, final Boolean penaltyGracePeriodMissedInstallments, + final Boolean panelRequestApplicable, final Boolean noPenaltyNomineeHeirClosure, final Boolean deceasedSettlement, + final Boolean allowDebitTxnAfterDueDate) { + this.accountType = accountType; + this.customerSegment = customerSegment; + this.fdType = fdType; + this.productCode = productCode; + this.effectiveDate = effectiveDate; + this.effectiveTo = effectiveTo; + this.endDate = endDate; + this.introducerRequired = introducerRequired; + this.shareLinkingEnabled = shareLinkingEnabled; + this.nominationFacility = nominationFacility; + this.dormancyAlert = dormancyAlert; + this.isDormancyTrackingActive = isDormancyTrackingActive; + this.daysToInactive = daysToInactive; + this.daysToDormancy = daysToDormancy; + this.daysToEscheat = daysToEscheat; + this.interestCalculateSelector = interestCalculateSelector; + this.interestCalculationTypeName = interestCalculationTypeName; + this.averagePeriod = averagePeriod; + this.autoPost = autoPost; + this.frequencyFixDay = frequencyFixDay; + this.lastInterestDate = lastInterestDate; + this.enableLockinPeriod = enableLockinPeriod; + this.balanceFrequency = balanceFrequency; + this.accountClosedZeroBalanceInDayClosing = accountClosedZeroBalanceInDayClosing; + this.balanceSwapOption = balanceSwapOption; + this.msme = msme; + this.autoDebitFacility = autoDebitFacility; + this.loanFacility = loanFacility; + this.prematureWithdrawalOption = prematureWithdrawalOption; + this.tenureFlexibility = tenureFlexibility; + this.lienMarked = lienMarked; + this.maturityInstructions = maturityInstructions; + this.creditMaturityProceeds = creditMaturityProceeds; + this.penaltyGracePeriodMissedInstallments = penaltyGracePeriodMissedInstallments; + this.panelRequestApplicable = panelRequestApplicable; + this.noPenaltyNomineeHeirClosure = noPenaltyNomineeHeirClosure; + this.deceasedSettlement = deceasedSettlement; + this.allowDebitTxnAfterDueDate = allowDebitTxnAfterDueDate; + } + + public void copyCbsFieldsFrom(final FixedDepositAccountData src) { + if (src == null) { + return; + } + this.accountType = src.accountType; + this.customerSegment = src.customerSegment; + this.fdType = src.fdType; + this.accountTypeOptions = src.accountTypeOptions; + this.customerSegmentOptions = src.customerSegmentOptions; + this.productCode = src.productCode; + this.effectiveDate = src.effectiveDate; + this.effectiveTo = src.effectiveTo; + this.endDate = src.endDate; + this.introducerRequired = src.introducerRequired; + this.shareLinkingEnabled = src.shareLinkingEnabled; + this.nominationFacility = src.nominationFacility; + this.dormancyAlert = src.dormancyAlert; + this.isDormancyTrackingActive = src.isDormancyTrackingActive; + this.daysToInactive = src.daysToInactive; + this.daysToDormancy = src.daysToDormancy; + this.daysToEscheat = src.daysToEscheat; + this.interestCalculateSelector = src.interestCalculateSelector; + this.interestCalculationTypeName = src.interestCalculationTypeName; + this.averagePeriod = src.averagePeriod; + this.autoPost = src.autoPost; + this.frequencyFixDay = src.frequencyFixDay; + this.lastInterestDate = src.lastInterestDate; + this.enableLockinPeriod = src.enableLockinPeriod; + this.balanceFrequency = src.balanceFrequency; + this.accountClosedZeroBalanceInDayClosing = src.accountClosedZeroBalanceInDayClosing; + this.balanceSwapOption = src.balanceSwapOption; + this.msme = src.msme; + this.autoDebitFacility = src.autoDebitFacility; + this.loanFacility = src.loanFacility; + this.prematureWithdrawalOption = src.prematureWithdrawalOption; + this.tenureFlexibility = src.tenureFlexibility; + this.lienMarked = src.lienMarked; + this.maturityInstructions = src.maturityInstructions; + this.creditMaturityProceeds = src.creditMaturityProceeds; + this.penaltyGracePeriodMissedInstallments = src.penaltyGracePeriodMissedInstallments; + this.panelRequestApplicable = src.panelRequestApplicable; + this.noPenaltyNomineeHeirClosure = src.noPenaltyNomineeHeirClosure; + this.deceasedSettlement = src.deceasedSettlement; + this.allowDebitTxnAfterDueDate = src.allowDebitTxnAfterDueDate; + } + private FixedDepositAccountData(Long clientId, Long productId, Long fieldofficerId, LocalDate submittedOnDate, EnumOptionData interestCompoundingPeriodType, EnumOptionData interestPostingPeriodType, EnumOptionData interestCalculationType, EnumOptionData interestCalculationDaysInYearType, Integer lockinPeriodFrequency, EnumOptionData lockinPeriodFrequencyType, @@ -150,7 +310,7 @@ public final class FixedDepositAccountData extends DepositAccountData { final Collection paymentTypeOptions = null; final Collection savingsAccountDatas = null; - return new FixedDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId, + final FixedDepositAccountData result = new FixedDepositAccountData(depositAccountData.id, depositAccountData.accountNo, depositAccountData.externalId, depositAccountData.groupId, depositAccountData.groupName, depositAccountData.clientId, depositAccountData.clientName, depositAccountData.depositProductId, depositAccountData.depositProductName, depositAccountData.fieldOfficerId, depositAccountData.fieldOfficerName, depositAccountData.status, depositAccountData.timeline, depositAccountData.currency, @@ -170,11 +330,15 @@ public final class FixedDepositAccountData extends DepositAccountData { depositPeriod, depositPeriodFrequency, periodFrequencyTypeOptions, depositType, onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, linkedAccount, transferInterestToSavings, depositAccountData.withHoldTax, depositAccountData.taxGroup, maturityInstructionOptions, transferToSavingsId, transferToSavingsAccount); + if (depositAccountData instanceof FixedDepositAccountData) { + result.copyCbsFieldsFrom((FixedDepositAccountData) depositAccountData); + } + return result; } public static FixedDepositAccountData withInterestChart(final FixedDepositAccountData account, final DepositAccountInterestRateChartData accountChart) { - return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, + final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, @@ -192,6 +356,8 @@ public final class FixedDepositAccountData extends DepositAccountData { account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, account.savingsAccounts, account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions, account.transferToSavingsId, account.transferToSavingsAccount); + result.copyCbsFieldsFrom(account); + return result; } public static FixedDepositAccountData associationsAndTemplate(final FixedDepositAccountData account, FixedDepositAccountData template, @@ -202,7 +368,7 @@ public final class FixedDepositAccountData extends DepositAccountData { template = account; } - return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, + final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, @@ -220,6 +386,8 @@ public final class FixedDepositAccountData extends DepositAccountData { account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, template.savingsAccounts, linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions, account.transferToSavingsId, transferToSavingsAccount); + result.copyCbsFieldsFrom(account); + return result; } public static FixedDepositAccountData withTemplateOptions(final FixedDepositAccountData account, @@ -234,7 +402,7 @@ public final class FixedDepositAccountData extends DepositAccountData { final Collection periodFrequencyTypeOptions, final Collection savingsAccounts, final Collection maturityInstructionOptions) { - return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, + final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, @@ -251,6 +419,8 @@ public final class FixedDepositAccountData extends DepositAccountData { account.onAccountClosure, account.onAccountClosureOptions, account.paymentTypeOptions, savingsAccounts, account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, maturityInstructionOptions, account.transferToSavingsId, account.transferToSavingsAccount); + result.copyCbsFieldsFrom(account); + return result; } public static FixedDepositAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -427,7 +597,7 @@ public final class FixedDepositAccountData extends DepositAccountData { final Collection onAccountClosureOptions, final Collection paymentTypeOptions, final Collection savingsAccountDatas) { - return new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, + final FixedDepositAccountData result = new FixedDepositAccountData(account.id, account.accountNo, account.externalId, account.groupId, account.groupName, account.clientId, account.clientName, account.depositProductId, account.depositProductName, account.fieldOfficerId, account.fieldOfficerName, account.status, account.timeline, account.currency, account.nominalAnnualInterestRate, account.interestCompoundingPeriodType, account.interestPostingPeriodType, account.interestCalculationType, @@ -445,7 +615,8 @@ public final class FixedDepositAccountData extends DepositAccountData { account.onAccountClosure, onAccountClosureOptions, paymentTypeOptions, savingsAccountDatas, account.linkedAccount, account.transferInterestToSavings, account.withHoldTax, account.taxGroup, account.maturityInstructionOptions, account.transferToSavingsId, account.transferToSavingsAccount); - + result.copyCbsFieldsFrom(account); + return result; } private FixedDepositAccountData(final Long id, final String accountNo, final String externalId, final Long groupId, diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java index 2c61643e1..4da5b1860 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/data/FixedDepositProductData.java @@ -112,9 +112,9 @@ public final class FixedDepositProductData extends DepositProductData { } public static FixedDepositProductData withCharges(final FixedDepositProductData existingProduct, final Collection charges) { - return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, - existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, - existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, + final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, + existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, + existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions, @@ -131,6 +131,8 @@ public final class FixedDepositProductData extends DepositProductData { existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions); + result.copyCbsFieldsFrom(existingProduct); + return result; } /** @@ -152,9 +154,9 @@ public final class FixedDepositProductData extends DepositProductData { final Collection preClosurePenalInterestOnTypeOptions, final Collection periodFrequencyTypeOptions, final Collection taxGroupOptions) { - return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, - existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, - existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, + final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, + existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, + existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, currencyOptions, @@ -169,6 +171,8 @@ public final class FixedDepositProductData extends DepositProductData { existingProduct.inMultiplesOfDepositTermType, existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, taxGroupOptions); + result.copyCbsFieldsFrom(existingProduct); + return result; } public static FixedDepositProductData withAccountingDetails(final FixedDepositProductData existingProduct, @@ -189,9 +193,9 @@ public final class FixedDepositProductData extends DepositProductData { final Collection chargeOptions = null; final Collection penaltyOptions = null; - return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, - existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, - existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, + final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, + existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, + existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, @@ -206,6 +210,8 @@ public final class FixedDepositProductData extends DepositProductData { existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions); + result.copyCbsFieldsFrom(existingProduct); + return result; } public static FixedDepositProductData instance(final DepositProductData depositProductData, final boolean preClosurePenalApplicable, @@ -243,7 +249,7 @@ public final class FixedDepositProductData extends DepositProductData { depositProductData.interestCompoundingPeriodType, depositProductData.interestPostingPeriodType, depositProductData.interestCalculationType, depositProductData.interestCalculationDaysInYearType, depositProductData.lockinPeriodFrequency, depositProductData.lockinPeriodFrequencyType, - depositProductData.minBalanceForInterestCalculation, depositProductData.accountingRule, accountingMappings, + depositProductData.getMinBalanceForInterestCalculation(), depositProductData.accountingRule, accountingMappings, paymentChannelToFundSourceMappings, currencyOptions, interestCompoundingPeriodTypeOptions, interestPostingPeriodTypeOptions, interestCalculationTypeOptions, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, charges, chargeOptions, @@ -321,9 +327,9 @@ public final class FixedDepositProductData extends DepositProductData { public static FixedDepositProductData withInterestChart(final FixedDepositProductData existingProduct, final Collection interestRateCharts) { - return new FixedDepositProductData(existingProduct.id, existingProduct.name, existingProduct.shortName, existingProduct.description, - existingProduct.currency, existingProduct.nominalAnnualInterestRate, existingProduct.interestCompoundingPeriodType, - existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, + final FixedDepositProductData result = new FixedDepositProductData(existingProduct.id, existingProduct.name, + existingProduct.shortName, existingProduct.description, existingProduct.currency, existingProduct.nominalAnnualInterestRate, + existingProduct.interestCompoundingPeriodType, existingProduct.interestPostingPeriodType, existingProduct.interestCalculationType, existingProduct.interestCalculationDaysInYearType, existingProduct.lockinPeriodFrequency, existingProduct.lockinPeriodFrequencyType, existingProduct.minBalanceForInterestCalculation, existingProduct.accountingRule, existingProduct.accountingMappings, existingProduct.paymentChannelToFundSourceMappings, existingProduct.currencyOptions, @@ -340,7 +346,8 @@ public final class FixedDepositProductData extends DepositProductData { existingProduct.minDepositAmount, existingProduct.depositAmount, existingProduct.maxDepositAmount, existingProduct.periodFrequencyTypeOptions, existingProduct.withHoldTax, existingProduct.taxGroup, existingProduct.taxGroupOptions); - + result.copyCbsFieldsFrom(existingProduct); + return result; } private FixedDepositProductData(final Long id, final String name, final String shortName, final String description, @@ -445,4 +452,160 @@ public final class FixedDepositProductData extends DepositProductData { return preClosurePenalApplicable; } + // CBS Custom Fields + private Collection interestCalculationTypeOption; + private Collection periodFrequencyOptions; + private java.util.List yesNoOptions; + private java.util.List accountTypeOptions; + private java.util.List customerSegmentOptions; + private String productCode; + private String accountType; + private String fdType; + private Long glAccountId; + private String glAccountName; + private String customerSegment; + private Boolean introducerRequired; + private Boolean shareLinkingEnabled; + private Boolean nominationFacility; + private Boolean dormancyAlert; + private Boolean isDormancyTrackingActive; + private Long daysToInactive; + private Long daysToDormancy; + private Long daysToEscheat; + private String effectiveDate; + private String effectiveTo; + private String endDate; + private Integer interestCalculateSelector; + private String averagePeriod; + private Boolean autoPost; + private Integer frequencyFixDay; + private String lastInterestDate; + private Boolean enableLockinPeriod; + private Boolean autoDebitFacility; + private Boolean loanFacility; + private Boolean prematureWithdrawalOption; + private Boolean tenureFlexibility; + private Boolean lienMarked; + private Boolean maturityInstructions; + private Boolean creditMaturityProceeds; + private Boolean penaltyGracePeriodMissedInstallments; + private Boolean panelRequestApplicable; + private Boolean noPenaltyNomineeHeirClosure; + private Boolean deceasedSettlement; + private Boolean allowDebitTxnAfterDueDate; + private Boolean accountClosedZeroBalanceInDayClosing; + private String balanceFrequency; + + // Template option setters + public void setInterestCalculationTypeOption(Collection v) { this.interestCalculationTypeOption = v; } + public void setPeriodFrequencyOptions(Collection v) { this.periodFrequencyOptions = v; } + public void setYesNoOptions(java.util.List v) { this.yesNoOptions = v; } + public void setAccountTypeOptions(java.util.List v) { this.accountTypeOptions = v; } + public void setCustomerSegmentOptions(java.util.List v) { this.customerSegmentOptions = v; } + + // CBS field setters + public void setProductCode(String v) { this.productCode = v; } + public void setAccountType(String v) { this.accountType = v; } + public void setFdType(String v) { this.fdType = v; } + public void setGlAccountId(Long v) { this.glAccountId = v; } + public void setGlAccountName(String v) { this.glAccountName = v; } + public void setCustomerSegment(String v) { this.customerSegment = v; } + public void setIntroducerRequired(Boolean v) { this.introducerRequired = v; } + public void setShareLinkingEnabled(Boolean v) { this.shareLinkingEnabled = v; } + public void setNominationFacility(Boolean v) { this.nominationFacility = v; } + public void setDormancyAlert(Boolean v) { this.dormancyAlert = v; } + public void setIsDormancyTrackingActive(Boolean v) { this.isDormancyTrackingActive = v; } + public void setDaysToInactive(Long v) { this.daysToInactive = v; } + public void setDaysToDormancy(Long v) { this.daysToDormancy = v; } + public void setDaysToEscheat(Long v) { this.daysToEscheat = v; } + public void setEffectiveDate(String v) { this.effectiveDate = v; } + public void setEffectiveTo(String v) { this.effectiveTo = v; } + public void setEndDate(String v) { this.endDate = v; } + public void setInterestCalculateSelector(Integer v) { this.interestCalculateSelector = v; } + public void setAveragePeriod(String v) { this.averagePeriod = v; } + public void setAutoPost(Boolean v) { this.autoPost = v; } + public void setFrequencyFixDay(Integer v) { this.frequencyFixDay = v; } + public void setLastInterestDate(String v) { this.lastInterestDate = v; } + public void setEnableLockinPeriod(Boolean v) { this.enableLockinPeriod = v; } + public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; } + public void setLoanFacility(Boolean v) { this.loanFacility = v; } + public void setPrematureWithdrawalOption(Boolean v) { this.prematureWithdrawalOption = v; } + public void setTenureFlexibility(Boolean v) { this.tenureFlexibility = v; } + public void setLienMarked(Boolean v) { this.lienMarked = v; } + public void setMaturityInstructions(Boolean v) { this.maturityInstructions = v; } + public void setCreditMaturityProceeds(Boolean v) { this.creditMaturityProceeds = v; } + public void setPenaltyGracePeriodMissedInstallments(Boolean v) { this.penaltyGracePeriodMissedInstallments = v; } + public void setPanelRequestApplicable(Boolean v) { this.panelRequestApplicable = v; } + public void setNoPenaltyNomineeHeirClosure(Boolean v) { this.noPenaltyNomineeHeirClosure = v; } + public void setDeceasedSettlement(Boolean v) { this.deceasedSettlement = v; } + public void setAllowDebitTxnAfterDueDate(Boolean v) { this.allowDebitTxnAfterDueDate = v; } + public void setAccountClosedZeroBalanceInDayClosing(Boolean v) { this.accountClosedZeroBalanceInDayClosing = v; } + public void setBalanceFrequency(String v) { this.balanceFrequency = v; } + + // CBS field getters + public String getProductCode() { return productCode; } + public String getAccountType() { return accountType; } + public String getFdType() { return fdType; } + public Long getGlAccountId() { return glAccountId; } + public String getGlAccountName() { return glAccountName; } + public String getCustomerSegment() { return customerSegment; } + public Boolean getIntroducerRequired() { return introducerRequired; } + public Boolean getShareLinkingEnabled() { return shareLinkingEnabled; } + public Boolean getNominationFacility() { return nominationFacility; } + public Boolean getDormancyAlert() { return dormancyAlert; } + public Boolean getIsDormancyTrackingActive() { return isDormancyTrackingActive; } + public Long getDaysToInactive() { return daysToInactive; } + public Long getDaysToDormancy() { return daysToDormancy; } + public Long getDaysToEscheat() { return daysToEscheat; } + + public void copyCbsFieldsFrom(FixedDepositProductData src) { + if (src == null) return; + this.accountType = src.accountType; + this.fdType = src.fdType; + this.customerSegmentOptions = src.customerSegmentOptions; + this.productCode = src.productCode; + this.glAccountId = src.glAccountId; + this.glAccountName = src.glAccountName; + this.interestCalculationTypeOption = src.interestCalculationTypeOption; + this.periodFrequencyOptions = src.periodFrequencyOptions; + this.yesNoOptions = src.yesNoOptions; + this.accountTypeOptions = src.accountTypeOptions; + this.customerSegment = src.customerSegment; + this.introducerRequired = src.introducerRequired; + this.shareLinkingEnabled = src.shareLinkingEnabled; + this.nominationFacility = src.nominationFacility; + this.dormancyAlert = src.dormancyAlert; + this.isDormancyTrackingActive = src.isDormancyTrackingActive; + this.daysToInactive = src.daysToInactive; + this.daysToDormancy = src.daysToDormancy; + this.daysToEscheat = src.daysToEscheat; + this.effectiveDate = src.effectiveDate; + this.effectiveTo = src.effectiveTo; + this.endDate = src.endDate; + this.interestCalculateSelector = src.interestCalculateSelector; + this.averagePeriod = src.averagePeriod; + this.autoPost = src.autoPost; + this.frequencyFixDay = src.frequencyFixDay; + this.lastInterestDate = src.lastInterestDate; + this.enableLockinPeriod = src.enableLockinPeriod; + this.autoDebitFacility = src.autoDebitFacility; + this.loanFacility = src.loanFacility; + this.prematureWithdrawalOption = src.prematureWithdrawalOption; + this.tenureFlexibility = src.tenureFlexibility; + this.lienMarked = src.lienMarked; + this.maturityInstructions = src.maturityInstructions; + this.creditMaturityProceeds = src.creditMaturityProceeds; + this.penaltyGracePeriodMissedInstallments = src.penaltyGracePeriodMissedInstallments; + this.panelRequestApplicable = src.panelRequestApplicable; + this.noPenaltyNomineeHeirClosure = src.noPenaltyNomineeHeirClosure; + this.deceasedSettlement = src.deceasedSettlement; + this.allowDebitTxnAfterDueDate = src.allowDebitTxnAfterDueDate; + this.accountClosedZeroBalanceInDayClosing = src.accountClosedZeroBalanceInDayClosing; + this.balanceFrequency = src.balanceFrequency; + this.withdrawalFeeForTransfers = src.withdrawalFeeForTransfers; + } + + private Boolean withdrawalFeeForTransfers; + public void setWithdrawalFeeForTransfers(Boolean v) { this.withdrawalFeeForTransfers = v; } + public Boolean getWithdrawalFeeForTransfers() { return withdrawalFeeForTransfers; } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java index 485108b6c..96d4996d1 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/DepositProductAssembler.java @@ -187,6 +187,65 @@ public class DepositProductAssembler { // update product reference productTermAndPreClosure.updateProductReference(fixedDepositProduct); + // CBS Custom Fields + fixedDepositProduct.setAccountType(command.stringValueOfParameterNamed("accountType")); + fixedDepositProduct.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment")); + final String fdProductCode = command.stringValueOfParameterNamed("productCode"); + fixedDepositProduct.setProductCode((fdProductCode != null && !fdProductCode.isBlank()) ? fdProductCode : null); + final String fdType = command.stringValueOfParameterNamed("fdType"); + fixedDepositProduct.setFdType((fdType != null && !fdType.isBlank()) ? fdType : null); + if (command.parameterExists("glAccountId")) { + fixedDepositProduct.setGlAccountId(command.longValueOfParameterNamed("glAccountId")); + } + fixedDepositProduct.setIntroducerRequired(command.booleanPrimitiveValueOfParameterNamed("introducerRequired")); + fixedDepositProduct.setShareLinkingEnabled(command.booleanPrimitiveValueOfParameterNamed("shareLinkingEnabled")); + fixedDepositProduct.setEffectiveDate(parseCbsDate(command.stringValueOfParameterNamed("effectiveDate"))); + fixedDepositProduct.setEffectiveTo(parseCbsDate(command.stringValueOfParameterNamed("effectiveTo"))); + fixedDepositProduct.setEndDate(parseCbsDate(command.stringValueOfParameterNamed("endDate"))); + fixedDepositProduct.setNominationFacility(command.booleanPrimitiveValueOfParameterNamed("nominationFacility")); + final boolean dormancyAlertValue = command.booleanPrimitiveValueOfParameterNamed("dormancyAlert"); + fixedDepositProduct.setDormancyAlert(dormancyAlertValue); + // isDormancyTrackingActive is driven by dormancyAlert for FD products (same as RD) + fixedDepositProduct.setIsDormancyTrackingActive(dormancyAlertValue); + if (command.parameterExists("daysToInactive")) { + fixedDepositProduct.setDaysToInactive(command.longValueOfParameterNamed("daysToInactive")); + } + if (command.parameterExists("daysToDormancy")) { + fixedDepositProduct.setDaysToDormancy(command.longValueOfParameterNamed("daysToDormancy")); + } + if (command.parameterExists("daysToEscheat")) { + fixedDepositProduct.setDaysToEscheat(command.longValueOfParameterNamed("daysToEscheat")); + } + fixedDepositProduct.setInterestCalculateSelector(command.integerValueOfParameterNamed("interestCalculateSelector")); + fixedDepositProduct.setAveragePeriod(command.stringValueOfParameterNamed("averagePeriod")); + fixedDepositProduct.setAutoPost(command.booleanPrimitiveValueOfParameterNamed("autoPost")); + fixedDepositProduct.setFrequencyFixDay(command.integerValueOfParameterNamed("frequencyFixDay")); + fixedDepositProduct.setLastInterestDate(parseCbsDate(command.stringValueOfParameterNamed("lastInterestDate"))); + fixedDepositProduct.setEnableLockinPeriod(command.booleanPrimitiveValueOfParameterNamed("enableLockinPeriod")); + fixedDepositProduct.setAutoDebitFacility(command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility")); + fixedDepositProduct.setLoanFacility(command.booleanPrimitiveValueOfParameterNamed("loanFacility")); + fixedDepositProduct.setPrematureWithdrawalOption(command.booleanPrimitiveValueOfParameterNamed("prematureWithdrawalOption")); + fixedDepositProduct.setTenureFlexibility(command.booleanPrimitiveValueOfParameterNamed("tenureFlexibility")); + fixedDepositProduct.setLienMarked(command.booleanPrimitiveValueOfParameterNamed("lienMarked")); + fixedDepositProduct.setMaturityInstructions(command.booleanPrimitiveValueOfParameterNamed("maturityInstructions")); + fixedDepositProduct.setCreditMaturityProceeds(command.booleanPrimitiveValueOfParameterNamed("creditMaturityProceeds")); + fixedDepositProduct.setPenaltyGracePeriodMissedInstallments( + command.booleanPrimitiveValueOfParameterNamed("penaltyGracePeriodMissedInstallments")); + fixedDepositProduct.setPanelRequestApplicable(command.booleanPrimitiveValueOfParameterNamed("panelRequestApplicable")); + fixedDepositProduct.setNoPenaltyNomineeHeirClosure( + command.booleanPrimitiveValueOfParameterNamed("noPenaltyNomineeHeirClosure")); + fixedDepositProduct.setDeceasedSettlement(command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement")); + fixedDepositProduct.setAllowDebitTxnAfterDueDate(command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate")); + if (command.parameterExists("accountClosedZeroBalanceInDayClosing")) { + fixedDepositProduct.setAccountClosedZeroBalanceInDayClosing( + command.booleanPrimitiveValueOfParameterNamed("accountClosedZeroBalanceInDayClosing")); + } + fixedDepositProduct.setBalanceFrequency(command.stringValueOfParameterNamed("balanceFrequency")); + + if (command.parameterExists("withdrawalFeeForTransfers")) { + fixedDepositProduct.setWithdrawalFeeApplicableForTransfer(command.booleanPrimitiveValueOfParameterNamed("withdrawalFeeForTransfers")); + } + fixedDepositProduct.validateDomainRules(); return fixedDepositProduct; @@ -338,6 +397,10 @@ public class DepositProductAssembler { recurringDepositProduct.setDeceasedSettlement(command.booleanPrimitiveValueOfParameterNamed("deceasedSettlement")); recurringDepositProduct.setAllowDebitTxnAfterDueDate(command.booleanPrimitiveValueOfParameterNamed("allowDebitTxnAfterDueDate")); + if (command.parameterExists("withdrawalFeeForTransfers")) { + recurringDepositProduct.setWithdrawalFeeApplicableForTransfer(command.booleanPrimitiveValueOfParameterNamed("withdrawalFeeForTransfers")); + } + recurringDepositProduct.validateDomainRules(); return recurringDepositProduct; diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java index 216770856..8b398b5df 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositProduct.java @@ -26,6 +26,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.maxDepo import com.google.gson.JsonArray; import com.google.gson.JsonObject; import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -34,6 +35,7 @@ import jakarta.persistence.JoinTable; import jakarta.persistence.OneToMany; import jakarta.persistence.OneToOne; import jakarta.persistence.Transient; + import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; @@ -76,6 +78,17 @@ public class FixedDepositProduct extends SavingsProduct { @Transient protected InterestRateChartAssembler chartAssembler; + @Column(name = "fd_type", length = 50) + private String fdType; + + public String getFdType() { + return fdType; + } + + public void setFdType(String fdType) { + this.fdType = fdType; + } + protected FixedDepositProduct() { } @@ -161,6 +174,12 @@ public class FixedDepositProduct extends SavingsProduct { actualChanges.putAll(super.update(command)); + if (command.isChangeInStringParameterNamed("fdType", this.fdType)) { + final String newValue = command.stringValueOfParameterNamed("fdType"); + actualChanges.put("fdType", newValue); + this.fdType = (newValue != null && !newValue.isBlank()) ? newValue : null; + } + if (this.productTermAndPreClosure != null) { actualChanges.putAll(this.productTermAndPreClosure.update(command, baseDataValidator)); } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index 3526c6d9b..ccbb4fcbd 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -3957,6 +3957,13 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom @Column(name = "linked_account_no", length = 50) private String linkedAccountNo; + @Column(name = "nomination_facility") + private Boolean nominationFacility; + + @Column(name = "auto_debit_facility") + private Boolean autoDebitFacility; + + @jakarta.persistence.OneToOne(cascade = jakarta.persistence.CascadeType.ALL, mappedBy = "savingsAccount", orphanRemoval = true, fetch = FetchType.LAZY) private SavingsAccountFacilityConfig facilityConfig; @@ -4022,4 +4029,10 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom public List getJointHolders() { return jointHolders; } public void setJointHolders(List v) { this.jointHolders = v; } + + public Boolean getNominationFacility() { return nominationFacility; } + public void setNominationFacility(Boolean v) { this.nominationFacility = v; } + + public Boolean getAutoDebitFacility() { return autoDebitFacility; } + public void setAutoDebitFacility(Boolean v) { this.autoDebitFacility = v; } } diff --git a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java index 07022d85a..8bd5eb247 100644 --- a/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java +++ b/fineract-savings/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsProduct.java @@ -246,7 +246,7 @@ public class SavingsProduct extends AbstractPersistableCustom { @Column(name = "customer_segment", length = 50) private String customerSegment; - @Column(name = "product_code", length = 20, unique = true) + @Column(name = "product_code", length = 20) private String productCode; @Column(name = "gl_account_id") @@ -1034,6 +1034,10 @@ public class SavingsProduct extends AbstractPersistableCustom { return this.withdrawalFeeApplicableForTransfer; } + public void setWithdrawalFeeApplicableForTransfer(boolean withdrawalFeeApplicableForTransfer) { + this.withdrawalFeeApplicableForTransfer = withdrawalFeeApplicableForTransfer; + } + public boolean isAllowOverdraft() { return this.allowOverdraft; }