feat: update savings product backend changes

This commit is contained in:
Nitesh Yadav
2026-04-28 13:12:27 +05:30
parent 72adbf6305
commit dc4a720ae2
16 changed files with 17339 additions and 35 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -19,7 +19,27 @@
package org.apache.fineract.portfolio.savings.domain;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.SAVINGS_ACCOUNT_RESOURCE_NAME;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountFacilityConfigParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountStatusParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianClientIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.modeOfOperationParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianCifNumberParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.guardianRelationshipParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerCifNumberParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.introducerNameParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.jointHoldersParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountNoParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.linkedAccountTypeParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDateOfBirthParamName;
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.allowOverdraftParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
@@ -45,7 +65,10 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.submitte
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withHoldTaxParamName;
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.withdrawalFeeForTransfersParamName;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
@@ -336,9 +359,102 @@ public class SavingsAccountAssembler {
account.validateAccountValuesWithProduct();
// CBS extended fields
applyCbsFields(command, element, account);
return account;
}
private static final Gson GSON = new Gson();
private void applyCbsFields(final JsonCommand command, final JsonElement element, final SavingsAccount account) {
// Product classification
final String cbsAccountType = this.fromApiJsonHelper.extractStringNamed(accountTypeParamName, element);
if (cbsAccountType != null) account.setCbsAccountType(cbsAccountType);
final String customerSegment = this.fromApiJsonHelper.extractStringNamed(customerSegmentParamName, element);
if (customerSegment != null) account.setCustomerSegment(customerSegment);
// Status of Account
final String accountStatus = this.fromApiJsonHelper.extractStringNamed(accountStatusParamName, element);
if (accountStatus != null) account.setAccountStatus(accountStatus);
final String modeOfOperation = this.fromApiJsonHelper.extractStringNamed(modeOfOperationParamName, element);
if (modeOfOperation != null) account.setModeOfOperation(modeOfOperation);
account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element));
account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element));
account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element));
account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element));
account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element));
if (command.parameterExists(minorDateOfBirthParamName)) {
final LocalDate minorDob = this.fromApiJsonHelper.extractLocalDateNamed(minorDateOfBirthParamName, element);
account.setMinorDateOfBirth(minorDob);
}
final Long guardianClientId = this.fromApiJsonHelper.extractLongNamed(guardianClientIdParamName, element);
if (guardianClientId != null) account.setGuardianClientId(guardianClientId);
// Introducer
final Long introducerId = this.fromApiJsonHelper.extractLongNamed(introducerIdParamName, element);
if (introducerId != null) account.setIntroducerClientId(introducerId);
account.setIntroducerName(this.fromApiJsonHelper.extractStringNamed(introducerNameParamName, element));
account.setIntroducerCifNumber(this.fromApiJsonHelper.extractStringNamed(introducerCifNumberParamName, element));
// Nominee (stored as JSON string)
if (element.getAsJsonObject().has(nomineeParamName)) {
final JsonElement nomineeEl = element.getAsJsonObject().get(nomineeParamName);
if (nomineeEl != null && !nomineeEl.isJsonNull()) {
account.setNomineeData(nomineeEl.toString());
}
}
// Deposit Account Link
final Long linkedAccountId = this.fromApiJsonHelper.extractLongNamed(linkedAccountIdParamName, element);
if (linkedAccountId != null) account.setLinkedAccountId(linkedAccountId);
account.setLinkedAccountType(this.fromApiJsonHelper.extractStringNamed(linkedAccountTypeParamName, element));
account.setLinkedAccountNo(this.fromApiJsonHelper.extractStringNamed(linkedAccountNoParamName, element));
// Account Facility Config
if (element.getAsJsonObject().has(accountFacilityConfigParamName)) {
final JsonElement configEl = element.getAsJsonObject().get(accountFacilityConfigParamName);
if (configEl != null && !configEl.isJsonNull() && configEl.isJsonObject()) {
SavingsAccountFacilityConfig facilityConfig = account.getFacilityConfig();
if (facilityConfig == null) {
facilityConfig = new SavingsAccountFacilityConfig(account);
account.setFacilityConfig(facilityConfig);
}
facilityConfig.updateFrom(configEl.getAsJsonObject());
}
}
// Joint holders
if (element.getAsJsonObject().has(jointHoldersParamName)) {
final JsonElement holdersEl = element.getAsJsonObject().get(jointHoldersParamName);
if (holdersEl != null && !holdersEl.isJsonNull() && holdersEl.isJsonArray()) {
final com.google.gson.JsonArray holdersArray = holdersEl.getAsJsonArray();
final List<SavingsAccountJointHolder> holders = new ArrayList<>();
for (int i = 0; i < holdersArray.size(); i++) {
final JsonObject h = holdersArray.get(i).getAsJsonObject();
final Long clientId = h.has("clientId") && !h.get("clientId").isJsonNull()
? h.get("clientId").getAsLong() : null;
final String cifNumber = h.has("cifNumber") ? h.get("cifNumber").getAsString() : null;
final String displayName = h.has("displayName") ? h.get("displayName").getAsString() : null;
final BigDecimal sharePercentage = h.has("sharePercentage") && !h.get("sharePercentage").isJsonNull()
? h.get("sharePercentage").getAsBigDecimal() : null;
final String signingAuthority = h.has("signingAuthority") ? h.get("signingAuthority").getAsString() : null;
final String role = h.has("role") ? h.get("role").getAsString() : null;
final boolean isPrimary = h.has("isPrimary") && h.get("isPrimary").getAsBoolean();
holders.add(new SavingsAccountJointHolder(account, clientId, cifNumber, displayName,
sharePercentage, signingAuthority, role, isPrimary));
}
account.getJointHolders().clear();
account.getJointHolders().addAll(holders);
}
}
}
public SavingsAccount assembleFrom(final Long savingsId, final boolean backdatedTxnsAllowedTill) {
SavingsAccount account = this.savingsAccountRepository.findSavingsWithNotFoundDetection(savingsId, backdatedTxnsAllowedTill);
return loadTransactionsToSavingsAccount(account, backdatedTxnsAllowedTill);
@@ -215,7 +215,22 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
try {
final String sql = "select " + this.savingAccountMapper.schema() + " where sa.id = ?";
return this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper, new Object[] { accountId }); // NOSONAR
final SavingsAccountData accountData = this.jdbcTemplate.queryForObject(sql, this.savingAccountMapper,
new Object[] { accountId }); // NOSONAR
// Load joint holders
if (accountData != null) {
final String jointSql = "SELECT jh.id as id, jh.client_id as clientId, jh.cif_number as cifNumber, "
+ "jh.display_name as displayName, jh.share_percentage as sharePercentage, "
+ "jh.signing_authority as signingAuthority, jh.role as role, jh.is_primary as isPrimary "
+ "FROM m_savings_account_joint_holders jh WHERE jh.savings_account_id = ?";
final List<java.util.Map<String, Object>> jointHolders = this.jdbcTemplate.queryForList(jointSql, accountId);
if (!jointHolders.isEmpty()) {
accountData.setJointHolders(jointHolders);
}
}
return accountData;
} catch (final EmptyResultDataAccessException e) {
throw new SavingsAccountNotFoundException(accountId, e);
}
@@ -763,7 +778,43 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
sqlBuilder.append("sp.days_to_inactive as daysToInactive, ");
sqlBuilder.append("sp.days_to_dormancy as daysToDormancy, ");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat ");
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
// CBS extended fields
sqlBuilder.append("sa.cbs_account_type as cbsAccountType, ");
sqlBuilder.append("sa.customer_segment as customerSegment, ");
sqlBuilder.append("sa.account_status as accountStatus, ");
sqlBuilder.append("sa.mode_of_operation as modeOfOperation, ");
sqlBuilder.append("sa.special_category as specialCategory, ");
sqlBuilder.append("sa.weaker_section as weakerSection, ");
sqlBuilder.append("sa.guardian_name as guardianName, ");
sqlBuilder.append("sa.guardian_cif_number as guardianCifNumber, ");
sqlBuilder.append("sa.guardian_relationship as guardianRelationship, ");
sqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, ");
sqlBuilder.append("sa.guardian_client_id as guardianClientId, ");
sqlBuilder.append("sa.introducer_client_id as introducerClientId, ");
sqlBuilder.append("sa.introducer_name as introducerName, ");
sqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, ");
sqlBuilder.append("sa.nominee_data as nomineeData, ");
sqlBuilder.append("sa.linked_account_id as linkedAccountId, ");
sqlBuilder.append("sa.linked_account_type as linkedAccountType, ");
sqlBuilder.append("sa.linked_account_no as linkedAccountNo, ");
// Facility config fields
sqlBuilder.append("safc.atm_facility as atmFacility, ");
sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, ");
sqlBuilder.append("safc.upi_facility as upiFacility, ");
sqlBuilder.append("safc.pos_transactions_facility as posTransactionsFacility, ");
sqlBuilder.append("safc.cheque_book as chequeBook, ");
sqlBuilder.append("safc.imps_facility as impsFacility, ");
sqlBuilder.append("safc.sms_alerts as smsAlerts, ");
sqlBuilder.append("safc.bbps_facility as bbpsFacility, ");
sqlBuilder.append("safc.insurance_cover as insuranceCover, ");
sqlBuilder.append("safc.bepg_facility as bepgFacility, ");
sqlBuilder.append("safc.neft_facility as neftFacility, ");
sqlBuilder.append("safc.rtgs_facility as rtgsFacility, ");
sqlBuilder.append("safc.inter_bank_total_cap_facility as interBankTotalCapFacility, ");
sqlBuilder.append("safc.withdrawal_facility as withdrawalFacility, ");
sqlBuilder.append("safc.deposit_facility_kyc as depositFacilityKyc, ");
sqlBuilder.append("safc.virtual_debit_card as virtualDebitCard ");
sqlBuilder.append("from m_savings_account sa ");
sqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
sqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
@@ -777,6 +828,7 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
sqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid ");
sqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
sqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
sqlBuilder.append("left join m_savings_account_facility_config safc on safc.savings_account_id = sa.id ");
this.schemaSql = sqlBuilder.toString();
}
@@ -970,14 +1022,70 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
taxGroupData = TaxGroupData.lookup(taxGroupId, taxGroupName);
}
return SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId,
productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock, timeline, currency,
nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType, interestCalculationType,
interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency, lockinPeriodFrequencyType,
withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance,
maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft,
minOverdraftForInterestCalculation, withHoldTax, taxGroupData, lastActiveTransactionDate, isDormancyTrackingActive,
daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount);
final SavingsAccountData accountData = SavingsAccountData.instance(id, accountNo, depositType, externalId, groupId, groupName,
clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, reasonForBlock,
timeline, currency, nominalAnnualInterestRate, interestCompoundingPeriodType, interestPostingPeriodType,
interestCalculationType, interestCalculationDaysInYearType, minRequiredOpeningBalance, lockinPeriodFrequency,
lockinPeriodFrequencyType, withdrawalFeeForTransfers, summary, allowOverdraft, overdraftLimit, minRequiredBalance,
enforceMinRequiredBalance, maxAllowedLienLimit, lienAllowed, minBalanceForInterestCalculation, onHoldFunds,
nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroupData,
lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, onHoldAmount);
// CBS extended fields
accountData.setCbsAccountType(rs.getString("cbsAccountType"));
accountData.setCustomerSegment(rs.getString("customerSegment"));
accountData.setAccountStatus(rs.getString("accountStatus"));
accountData.setModeOfOperation(rs.getString("modeOfOperation"));
accountData.setSpecialCategory(rs.getString("specialCategory"));
accountData.setWeakerSection(rs.getString("weakerSection"));
accountData.setGuardianName(rs.getString("guardianName"));
accountData.setGuardianCifNumber(rs.getString("guardianCifNumber"));
accountData.setGuardianRelationship(rs.getString("guardianRelationship"));
accountData.setMinorDateOfBirth(JdbcSupport.getLocalDate(rs, "minorDateOfBirth"));
accountData.setGuardianClientId(JdbcSupport.getLong(rs, "guardianClientId"));
accountData.setIntroducerClientId(JdbcSupport.getLong(rs, "introducerClientId"));
accountData.setIntroducerName(rs.getString("introducerName"));
accountData.setIntroducerCifNumber(rs.getString("introducerCifNumber"));
accountData.setLinkedAccountId(JdbcSupport.getLong(rs, "linkedAccountId"));
accountData.setLinkedAccountType(rs.getString("linkedAccountType"));
accountData.setLinkedAccountNo(rs.getString("linkedAccountNo"));
// Nominee as parsed map
final String nomineeDataStr = rs.getString("nomineeData");
if (nomineeDataStr != null && !nomineeDataStr.isEmpty()) {
try {
com.google.gson.Gson gson = new com.google.gson.Gson();
@SuppressWarnings("unchecked")
java.util.Map<String, Object> nomineeMap = gson.fromJson(nomineeDataStr,
new com.google.gson.reflect.TypeToken<java.util.Map<String, Object>>() {}.getType());
accountData.setNomineeData(nomineeMap);
} catch (Exception ignored) {}
}
// Facility config
final boolean hasFacilityConfig = rs.getObject("atmFacility") != null;
if (hasFacilityConfig) {
final java.util.Map<String, Object> facilityConfigMap = new java.util.LinkedHashMap<>();
facilityConfigMap.put("atmFacility", rs.getBoolean("atmFacility"));
facilityConfigMap.put("atmWithdrawalFacility", rs.getBoolean("atmWithdrawalFacility"));
facilityConfigMap.put("upiFacility", rs.getBoolean("upiFacility"));
facilityConfigMap.put("posTransactionsFacility", rs.getBoolean("posTransactionsFacility"));
facilityConfigMap.put("chequeBook", rs.getBoolean("chequeBook"));
facilityConfigMap.put("impsFacility", rs.getBoolean("impsFacility"));
facilityConfigMap.put("smsAlerts", rs.getBoolean("smsAlerts"));
facilityConfigMap.put("bbpsFacility", rs.getBoolean("bbpsFacility"));
facilityConfigMap.put("insuranceCover", rs.getBoolean("insuranceCover"));
facilityConfigMap.put("bepgFacility", rs.getBoolean("bepgFacility"));
facilityConfigMap.put("neftFacility", rs.getBoolean("neftFacility"));
facilityConfigMap.put("rtgsFacility", rs.getBoolean("rtgsFacility"));
facilityConfigMap.put("interBankTotalCapFacility", rs.getBoolean("interBankTotalCapFacility"));
facilityConfigMap.put("withdrawalFacility", rs.getBoolean("withdrawalFacility"));
facilityConfigMap.put("depositFacilityKyc", rs.getBoolean("depositFacilityKyc"));
facilityConfigMap.put("virtualDebitCard", rs.getBoolean("virtualDebitCard"));
accountData.setAccountFacilityConfig(facilityConfigMap);
}
return accountData;
}
}
@@ -263,6 +263,8 @@
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductDepositLimitKyc</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsAccountFacilityConfig</class>
<class>org.apache.fineract.portfolio.savings.domain.SavingsAccountJointHolder</class>
<!-- Progressive Loan Module Entities -->
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance</class>