current account save api update

This commit is contained in:
Nitesh Yadav
2026-04-29 18:07:37 +05:30
parent a7965f8792
commit 15fa25f463
12 changed files with 12312 additions and 180 deletions
@@ -220,7 +220,7 @@ public class ClientApiConstants {
public static final String clientEntityName = "clients";
public static final Set<String> CLIENT_ACCOUNTS_DATA_PARAMETERS = new HashSet<>(
Arrays.asList("loanAccounts", "savingsAccounts", "shareAccounts"));
Arrays.asList("loanAccounts", "savingsAccounts", "shareAccounts", "currentAccounts"));
/**
* These parameters will match the class level parameters of ClientData. Where possible, we try to get response
@@ -0,0 +1,29 @@
/**
* 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.currentaccount.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourceNotFoundException;
public class CurrentAccountNotFoundException extends AbstractPlatformResourceNotFoundException {
public CurrentAccountNotFoundException(Long id) {
super("error.msg.current.account.id.invalid",
"Current account with identifier " + id + " does not exist", id);
}
}
@@ -27,7 +27,7 @@ import org.springframework.stereotype.Component;
/**
* Generates CBS account numbers for current accounts.
* Format: 00 + GL_CODE(6 digits) + OFFICE_ID(2 digits) + ACCOUNT_ID(5 digits) = 15 digits
* GL code is fetched from m_current_product.gl_account_id → m_acc_gl_account.gl_code
* GL code is fetched from m_current_product.gl_account_id → acc_gl_account.gl_code
*/
@Component
@RequiredArgsConstructor
@@ -43,7 +43,7 @@ public class CurrentAccountNumberGenerator {
// Fetch gl_code directly from current product → gl account
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN m_acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "JOIN acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
@@ -69,7 +69,7 @@ public class CurrentAccountNumberGenerator {
public String preview(Long productId, Long officeId) {
String glCode = jdbcTemplate.query(
"SELECT ga.gl_code FROM m_current_product cp "
+ "JOIN m_acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "JOIN acc_gl_account ga ON ga.id = cp.gl_account_id "
+ "WHERE cp.id = ?",
rs -> rs.next() ? rs.getString("gl_code") : null,
productId);
@@ -0,0 +1,29 @@
/**
* 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.currentaccount.service;
import java.util.List;
import java.util.Map;
public interface CurrentAccountReadPlatformService {
Map<String, Object> retrieveOne(Long accountId);
List<Map<String, Object>> retrieveAll();
}
File diff suppressed because it is too large Load Diff
@@ -31,6 +31,7 @@ public class AccountSummaryCollectionData {
private final Collection<SavingsAccountSummaryData> savingsAccounts;
private final Collection<ShareAccountSummaryData> shareAccounts;
private final Collection<GuarantorAccountSummaryData> guarantorAccounts;
private final Collection<SavingsAccountSummaryData> currentAccounts;
private final Collection<LoanAccountSummaryData> memberLoanAccounts;
private final Collection<SavingsAccountSummaryData> memberSavingsAccounts;
@@ -49,6 +50,24 @@ public class AccountSummaryCollectionData {
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.shareAccounts = defaultShareAccountsIfEmpty(shareAccounts);
this.guarantorAccounts = guarantorAccounts;
this.currentAccounts = null;
this.memberLoanAccounts = null;
this.memberSavingsAccounts = null;
this.memberGuarantorAccounts = null;
}
public AccountSummaryCollectionData(final Collection<LoanAccountSummaryData> loanAccounts,
final Collection<LoanAccountSummaryData> groupLoanIndividualMonitoringAccounts,
final Collection<SavingsAccountSummaryData> savingsAccounts, final Collection<ShareAccountSummaryData> shareAccounts,
final Collection<GuarantorAccountSummaryData> guarantorAccounts,
final Collection<SavingsAccountSummaryData> currentAccounts) {
this.loanAccounts = defaultLoanAccountsIfEmpty(loanAccounts);
this.groupLoanIndividualMonitoringAccounts = groupLoanIndividualMonitoringAccounts;
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.shareAccounts = defaultShareAccountsIfEmpty(shareAccounts);
this.guarantorAccounts = guarantorAccounts;
this.currentAccounts = defaultSavingsAccountsIfEmpty(currentAccounts);
this.memberLoanAccounts = null;
this.memberSavingsAccounts = null;
this.memberGuarantorAccounts = null;
@@ -66,6 +85,7 @@ public class AccountSummaryCollectionData {
this.savingsAccounts = defaultSavingsAccountsIfEmpty(savingsAccounts);
this.guarantorAccounts = guarantorAccounts;
this.shareAccounts = null;
this.currentAccounts = null;
this.memberLoanAccounts = defaultLoanAccountsIfEmpty(memberLoanAccounts);
this.memberSavingsAccounts = defaultSavingsAccountsIfEmpty(memberSavingsAccounts);
this.memberGuarantorAccounts = defaultGuarantorAccountsIfEmpty(memberGuarantorAccounts);
@@ -79,7 +79,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
final List<ShareAccountSummaryData> shareAccounts = retrieveShareAccountDetails(clientId);
final List<GuarantorAccountSummaryData> guarantorloanAccounts = retrieveGuarantorLoanAccountDetails(guarantorWhereClause,
new Object[] { clientId });
return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts);
final List<SavingsAccountSummaryData> currentAccounts = retrieveCurrentAccountDetails(clientId);
return new AccountSummaryCollectionData(loanAccounts, glimAccounts, savingsAccounts, shareAccounts, guarantorloanAccounts, currentAccounts);
}
@Override
@@ -176,6 +177,12 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
return retrieveLoanAccountDetails(loanWhereClause, new Object[] { groupId });
}
private List<SavingsAccountSummaryData> retrieveCurrentAccountDetails(final Long clientId) {
final CurrentAccountSummaryDataMapper mapper = new CurrentAccountSummaryDataMapper();
final String sql = "select " + mapper.schema() + " where ca.client_id = ? order by ca.status_enum ASC, ca.account_no ASC";
return this.jdbcTemplate.query(sql, mapper, new Object[] { clientId }); // NOSONAR
}
private List<LoanAccountSummaryData> retrieveLoanAccountDetails(final String loanwhereClause, final Object[] inputs) {
final LoanAccountSummaryDataMapper rm = new LoanAccountSummaryDataMapper();
final String sql = "select " + rm.loanAccountSummarySchema() + loanwhereClause;
@@ -452,6 +459,119 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
}
}
private static final class CurrentAccountSummaryDataMapper implements RowMapper<SavingsAccountSummaryData> {
private final String schemaSql;
CurrentAccountSummaryDataMapper() {
final StringBuilder sb = new StringBuilder();
sb.append("ca.id as id, ca.account_no as accountNo, ca.external_id as externalId, ");
sb.append("ca.status_enum as statusEnum, ca.sub_status_enum as subStatusEnum, ");
sb.append("ca.account_type_enum as accountType, ");
sb.append("ca.account_balance_derived as accountBalance, ");
sb.append("ca.on_hold_funds_derived as onHoldFunds, ");
sb.append("ca.submittedon_date as submittedOnDate, ");
sb.append("sbu.username as submittedByUsername, sbu.firstname as submittedByFirstname, sbu.lastname as submittedByLastname, ");
sb.append("ca.rejectedon_date as rejectedOnDate, ");
sb.append("rbu.username as rejectedByUsername, rbu.firstname as rejectedByFirstname, rbu.lastname as rejectedByLastname, ");
sb.append("ca.withdrawnon_date as withdrawnOnDate, ");
sb.append("wbu.username as withdrawnByUsername, wbu.firstname as withdrawnByFirstname, wbu.lastname as withdrawnByLastname, ");
sb.append("ca.approvedon_date as approvedOnDate, ");
sb.append("abu.username as approvedByUsername, abu.firstname as approvedByFirstname, abu.lastname as approvedByLastname, ");
sb.append("ca.activatedon_date as activatedOnDate, ");
sb.append("avbu.username as activatedByUsername, avbu.firstname as activatedByFirstname, avbu.lastname as activatedByLastname, ");
sb.append("ca.closedon_date as closedOnDate, ");
sb.append("cbu.username as closedByUsername, cbu.firstname as closedByFirstname, cbu.lastname as closedByLastname, ");
sb.append("ca.currency_code as currencyCode, ca.currency_digits as currencyDigits, ca.currency_multiplesof as inMultiplesOf, ");
sb.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ");
sb.append("curr.display_symbol as currencyDisplaySymbol, ");
sb.append("ca.product_id as productId, cp.name as productName, cp.short_name as shortProductName ");
sb.append("from m_current_account ca ");
sb.append("join m_current_product cp on cp.id = ca.product_id ");
sb.append("join m_currency curr on curr.code = ca.currency_code ");
sb.append("left join m_appuser sbu on sbu.id = ca.submittedon_userid ");
sb.append("left join m_appuser rbu on rbu.id = ca.rejectedon_userid ");
sb.append("left join m_appuser wbu on wbu.id = ca.withdrawnon_userid ");
sb.append("left join m_appuser abu on abu.id = ca.approvedon_userid ");
sb.append("left join m_appuser avbu on avbu.id = ca.activatedon_userid ");
sb.append("left join m_appuser cbu on cbu.id = ca.closedon_userid ");
this.schemaSql = sb.toString();
}
public String schema() {
return this.schemaSql;
}
@Override
public SavingsAccountSummaryData mapRow(final ResultSet rs, final int rowNum) throws SQLException {
final Long id = JdbcSupport.getLong(rs, "id");
final String accountNo = rs.getString("accountNo");
final String externalId = rs.getString("externalId");
final Long productId = JdbcSupport.getLong(rs, "productId");
final String productName = rs.getString("productName");
final String shortProductName = rs.getString("shortProductName");
final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum");
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId);
final Integer subStatusEnum = JdbcSupport.getInteger(rs, "subStatusEnum");
final SavingsAccountSubStatusEnumData subStatus = SavingsEnumerations.subStatus(subStatusEnum);
final Integer accountType = JdbcSupport.getInteger(rs, "accountType");
final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType);
final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance");
final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds");
final String currencyCode = rs.getString("currencyCode");
final String currencyName = rs.getString("currencyName");
final String currencyNameCode = rs.getString("currencyNameCode");
final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol");
final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits");
final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf");
final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf,
currencyDisplaySymbol, currencyNameCode);
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
final String submittedByUsername = rs.getString("submittedByUsername");
final String submittedByFirstname = rs.getString("submittedByFirstname");
final String submittedByLastname = rs.getString("submittedByLastname");
final LocalDate rejectedOnDate = JdbcSupport.getLocalDate(rs, "rejectedOnDate");
final String rejectedByUsername = rs.getString("rejectedByUsername");
final String rejectedByFirstname = rs.getString("rejectedByFirstname");
final String rejectedByLastname = rs.getString("rejectedByLastname");
final LocalDate withdrawnOnDate = JdbcSupport.getLocalDate(rs, "withdrawnOnDate");
final String withdrawnByUsername = rs.getString("withdrawnByUsername");
final String withdrawnByFirstname = rs.getString("withdrawnByFirstname");
final String withdrawnByLastname = rs.getString("withdrawnByLastname");
final LocalDate approvedOnDate = JdbcSupport.getLocalDate(rs, "approvedOnDate");
final String approvedByUsername = rs.getString("approvedByUsername");
final String approvedByFirstname = rs.getString("approvedByFirstname");
final String approvedByLastname = rs.getString("approvedByLastname");
final LocalDate activatedOnDate = JdbcSupport.getLocalDate(rs, "activatedOnDate");
final String activatedByUsername = rs.getString("activatedByUsername");
final String activatedByFirstname = rs.getString("activatedByFirstname");
final String activatedByLastname = rs.getString("activatedByLastname");
final LocalDate closedOnDate = JdbcSupport.getLocalDate(rs, "closedOnDate");
final String closedByUsername = rs.getString("closedByUsername");
final String closedByFirstname = rs.getString("closedByFirstname");
final String closedByLastname = rs.getString("closedByLastname");
final SavingsAccountApplicationTimelineData timeline = new SavingsAccountApplicationTimelineData(submittedOnDate,
submittedByUsername, submittedByFirstname, submittedByLastname, rejectedOnDate, rejectedByUsername,
rejectedByFirstname, rejectedByLastname, withdrawnOnDate, withdrawnByUsername, withdrawnByFirstname,
withdrawnByLastname, approvedOnDate, approvedByUsername, approvedByFirstname, approvedByLastname, activatedOnDate,
activatedByUsername, activatedByFirstname, activatedByLastname, closedOnDate, closedByUsername, closedByFirstname,
closedByLastname);
final EnumOptionData depositTypeData = SavingsEnumerations.depositType(400);
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
accountBalance, onHoldFunds, null, accountBalance, accountTypeData, timeline, depositTypeData, subStatus, activatedOnDate);
}
}
private static final class LoanAccountSummaryDataMapper implements RowMapper<LoanAccountSummaryData> {
public String loanAccountSummarySchema() {
@@ -35,33 +35,26 @@ import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.UriInfo;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParser;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.exception.UnrecognizedQueryParamException;
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
import org.apache.fineract.infrastructure.core.service.Page;
import org.apache.fineract.infrastructure.core.service.SearchParameters;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.service.SqlValidator;
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountData;
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
import org.apache.fineract.portfolio.savings.service.CurrentAccountTemplateReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
import org.apache.fineract.portfolio.savings.service.SavingsAccountReadPlatformService;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
@Path("/v1/currentaccounts")
@Component
@@ -69,14 +62,15 @@ import org.springframework.util.CollectionUtils;
@RequiredArgsConstructor
public class CurrentAccountsApiResource {
private final SavingsAccountReadPlatformService savingsAccountReadPlatformService;
private static final Gson GSON = new GsonBuilder().serializeNulls().create();
private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService;
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final SqlValidator sqlValidator;
private final CurrentAccountWritePlatformService currentAccountWritePlatformService;
private final CurrentAccountReadPlatformService currentAccountReadPlatformService;
@GET
@Path("template")
@@ -110,14 +104,8 @@ public class CurrentAccountsApiResource {
@QueryParam("sortOrder") final String sortOrder) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
sqlValidator.validate(orderBy);
sqlValidator.validate(sortOrder);
sqlValidator.validate(externalId);
final SearchParameters searchParameters = SearchParameters.builder().limit(limit).externalId(externalId).offset(offset)
.orderBy(orderBy).sortOrder(sortOrder).build();
final Page<SavingsAccountData> products = savingsAccountReadPlatformService.retrieveAll(searchParameters);
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
return toApiJsonSerializer.serialize(settings, products, SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
final List<Map<String, Object>> accounts = currentAccountReadPlatformService.retrieveAll();
return GSON.toJson(accounts);
}
@POST
@@ -127,7 +115,8 @@ public class CurrentAccountsApiResource {
@ApiResponse(responseCode = "200", description = "OK")
public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}"));
final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command);
return toApiJsonSerializer.serialize(result);
}
@@ -144,13 +133,8 @@ public class CurrentAccountsApiResource {
@Context final UriInfo uriInfo) {
context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.CURRENT_ACCOUNT_RESOURCE_NAME);
if (!(is(chargeStatus, "all") || is(chargeStatus, "active") || is(chargeStatus, "inactive"))) {
throw new UnrecognizedQueryParamException("status", chargeStatus, new Object[] { "all", "active", "inactive" });
}
final SavingsAccountData savingsAccount = savingsAccountReadPlatformService.retrieveOne(accountId);
return populateTemplateAndAssociations(accountId, savingsAccount, staffInSelectedOfficeOnly, chargeStatus, uriInfo);
final Map<String, Object> account = currentAccountReadPlatformService.retrieveOne(accountId);
return GSON.toJson(account);
}
@PUT
@@ -164,7 +148,8 @@ public class CurrentAccountsApiResource {
@QueryParam("command") final String commandParam) {
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
JsonParser.parseString(apiRequestBodyAsJson != null ? apiRequestBodyAsJson : "{}"));
final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command);
return toApiJsonSerializer.serialize(result);
}
@@ -185,7 +170,8 @@ public class CurrentAccountsApiResource {
}
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(jsonApiRequest);
org.apache.fineract.infrastructure.core.api.JsonCommand.fromJsonElement(null,
JsonParser.parseString(jsonApiRequest));
CommandProcessingResult result = null;
if (is(commandParam, "reject")) {
@@ -225,42 +211,4 @@ public class CurrentAccountsApiResource {
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
}
private String populateTemplateAndAssociations(final Long accountId, final SavingsAccountData savingsAccount,
final boolean staffInSelectedOfficeOnly, final String chargeStatus, final UriInfo uriInfo) {
Collection<SavingsAccountTransactionData> transactions = null;
Collection<SavingsAccountChargeData> charges = null;
final Set<String> associationParameters = ApiParameterHelper.extractAssociationsForResponseIfProvided(uriInfo.getQueryParameters());
if (!associationParameters.isEmpty()) {
if (associationParameters.contains("all")) {
associationParameters.addAll(Arrays.asList(SavingsApiConstants.transactions, SavingsApiConstants.charges));
}
if (associationParameters.contains(SavingsApiConstants.transactions)) {
final Collection<SavingsAccountTransactionData> currentTransactions = savingsAccountReadPlatformService
.retrieveAllTransactions(accountId, DepositAccountType.CURRENT_DEPOSIT);
if (!CollectionUtils.isEmpty(currentTransactions)) {
transactions = currentTransactions;
}
}
if (associationParameters.contains(SavingsApiConstants.charges)) {
final Collection<SavingsAccountChargeData> currentCharges = savingsAccountChargeReadPlatformService
.retrieveSavingsAccountCharges(accountId, chargeStatus);
if (!CollectionUtils.isEmpty(currentCharges)) {
charges = currentCharges;
}
}
}
SavingsAccountData templateData = null;
final ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
if (settings.isTemplate()) {
templateData = currentAccountTemplateReadPlatformService.retrieveTemplate(savingsAccount.getClientId(),
savingsAccount.getGroupId(), savingsAccount.getSavingsProductId(), staffInSelectedOfficeOnly);
}
return toApiJsonSerializer.serialize(settings,
SavingsAccountData.withTemplateOptions(savingsAccount, templateData, transactions, charges),
SavingsApiSetConstants.SAVINGS_ACCOUNT_RESPONSE_DATA_PARAMETERS);
}
}
@@ -0,0 +1,375 @@
/**
* 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.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.portfolio.currentaccount.exception.CurrentAccountNotFoundException;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountReadPlatformService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountReadPlatformService {
private static final String BASE_SQL =
"SELECT ca.*, "
+ "c.display_name AS client_name, c.account_no AS client_account_no, "
+ "o.name AS office_name, "
+ "s.display_name AS staff_name, "
+ "cp.name AS product_name, cp.short_name AS product_short_name, "
+ "curr.name AS currency_name, curr.display_symbol AS currency_display_symbol, "
+ "curr.internationalized_name_code AS currency_name_code "
+ "FROM m_current_account ca "
+ "LEFT JOIN m_client c ON c.id = ca.client_id "
+ "LEFT JOIN m_office o ON o.id = ca.office_id "
+ "LEFT JOIN m_staff s ON s.id = ca.staff_id "
+ "LEFT JOIN m_current_product cp ON cp.id = ca.product_id "
+ "LEFT JOIN m_currency curr ON curr.code = ca.currency_code ";
private final JdbcTemplate jdbcTemplate;
@Override
public Map<String, Object> retrieveOne(Long accountId) {
List<Map<String, Object>> rows = jdbcTemplate.query(
BASE_SQL + "WHERE ca.id = ?",
new FlatRowMapper(),
accountId);
if (rows.isEmpty()) {
throw new CurrentAccountNotFoundException(accountId);
}
return buildStructuredResponse(rows.get(0));
}
@Override
public List<Map<String, Object>> retrieveAll() {
List<Map<String, Object>> rows = jdbcTemplate.query(BASE_SQL, new FlatRowMapper());
List<Map<String, Object>> result = new ArrayList<>();
for (Map<String, Object> row : rows) {
result.add(buildStructuredResponse(row));
}
return result;
}
private Map<String, Object> buildStructuredResponse(Map<String, Object> row) {
Map<String, Object> out = new LinkedHashMap<>();
out.put("id", row.get("id"));
out.put("accountNo", row.get("account_no"));
out.put("externalId", row.get("external_id"));
out.put("clientId", row.get("client_id"));
out.put("clientName", row.get("client_name"));
out.put("clientAccountNo", row.get("client_account_no"));
out.put("groupId", row.get("group_id"));
out.put("productId", row.get("product_id"));
out.put("productName", row.get("product_name"));
out.put("shortProductName", row.get("product_short_name"));
out.put("officeId", row.get("office_id"));
out.put("officeName", row.get("office_name"));
out.put("staffId", row.get("staff_id"));
out.put("fieldOfficerName", row.get("staff_name"));
// status
int statusEnum = toInt(row.get("status_enum"), 100);
out.put("status", buildStatus(statusEnum));
// subStatus
int subStatusEnum = toInt(row.get("sub_status_enum"), 0);
out.put("subStatus", buildSubStatus(subStatusEnum));
// accountType
int accountTypeEnum = toInt(row.get("account_type_enum"), 1);
out.put("accountType", buildAccountType(accountTypeEnum));
// timeline
out.put("timeline", buildTimeline(row));
// currency
out.put("currency", buildCurrency(row));
// interest settings
out.put("nominalAnnualInterestRate", row.get("nominal_annual_interest_rate"));
int compPeriod = toInt(row.get("interest_compounding_period_enum"), 4);
out.put("interestCompoundingPeriodType", buildEnumOption(compPeriod, interestCompoundingPeriodValue(compPeriod)));
int postingPeriod = toInt(row.get("interest_posting_period_enum"), 4);
out.put("interestPostingPeriodType", buildEnumOption(postingPeriod, interestPostingPeriodValue(postingPeriod)));
int calcType = toInt(row.get("interest_calculation_type_enum"), 1);
out.put("interestCalculationType", buildEnumOption(calcType, interestCalcTypeValue(calcType)));
int daysInYear = toInt(row.get("interest_calculation_days_in_year_type_enum"), 365);
out.put("interestCalculationDaysInYearType", buildEnumOption(daysInYear, daysInYearValue(daysInYear)));
out.put("minBalanceForInterestCalculation", row.get("min_balance_for_interest_calculation"));
// balance/overdraft
out.put("allowOverdraft", row.get("allow_overdraft"));
out.put("overdraftLimit", row.get("overdraft_limit"));
out.put("minOverdraftForInterestCalculation", row.get("min_overdraft_for_interest_calculation"));
out.put("minRequiredBalance", row.get("min_required_balance"));
out.put("enforceMinRequiredBalance", row.get("enforce_min_required_balance"));
out.put("onHoldFunds", row.get("on_hold_funds_derived"));
out.put("withHoldTax", row.get("withhold_tax"));
out.put("taxGroup", null);
// summary
out.put("summary", buildSummary(row));
// CBS-specific fields
out.put("cbsAccountType", row.get("cbs_account_type"));
out.put("customerSegment", row.get("customer_segment"));
out.put("accountStatus", row.get("account_status"));
out.put("modeOfOperation", row.get("mode_of_operation"));
out.put("specialCategory", row.get("special_category"));
out.put("msme", row.get("msme"));
out.put("autoDebitFacility", row.get("auto_debit_facility"));
out.put("balanceSwapOption", row.get("balance_swap_option"));
out.put("establishmentDate", row.get("establishment_date"));
out.put("startOperationDate", row.get("start_operation_date"));
out.put("nonIndividualClient", row.get("is_non_individual_client"));
out.put("signatoryRights", row.get("signatory_rights"));
out.put("introducerClientId", row.get("introducer_client_id"));
out.put("introducerName", row.get("introducer_name"));
out.put("introducerCifNumber", row.get("introducer_cif_number"));
out.put("nomineeData", row.get("nominee_data"));
return out;
}
private Map<String, Object> buildStatus(int statusEnum) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("id", statusEnum);
switch (statusEnum) {
case 100 -> {
s.put("code", "savingsAccountStatusType.submittedAndPendingApproval");
s.put("value", "Submitted and Pending Approval");
s.put("submittedAndPendingApproval", true);
s.put("approved", false); s.put("active", false);
s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false);
}
case 200 -> {
s.put("code", "savingsAccountStatusType.approved");
s.put("value", "Approved");
s.put("submittedAndPendingApproval", false);
s.put("approved", true); s.put("active", false);
s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false);
}
case 300 -> {
s.put("code", "savingsAccountStatusType.active");
s.put("value", "Active");
s.put("submittedAndPendingApproval", false);
s.put("approved", false); s.put("active", true);
s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false);
}
case 400 -> {
s.put("code", "savingsAccountStatusType.withdrawn");
s.put("value", "Withdrawn by Applicant");
s.put("submittedAndPendingApproval", false);
s.put("approved", false); s.put("active", false);
s.put("rejected", false); s.put("withdrawnByApplicant", true); s.put("closed", false);
}
case 500 -> {
s.put("code", "savingsAccountStatusType.rejected");
s.put("value", "Rejected");
s.put("submittedAndPendingApproval", false);
s.put("approved", false); s.put("active", false);
s.put("rejected", true); s.put("withdrawnByApplicant", false); s.put("closed", false);
}
case 600 -> {
s.put("code", "savingsAccountStatusType.closed");
s.put("value", "Closed");
s.put("submittedAndPendingApproval", false);
s.put("approved", false); s.put("active", false);
s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", true);
}
default -> {
s.put("code", "savingsAccountStatusType.invalid");
s.put("value", "Invalid");
s.put("submittedAndPendingApproval", false);
s.put("approved", false); s.put("active", false);
s.put("rejected", false); s.put("withdrawnByApplicant", false); s.put("closed", false);
}
}
return s;
}
private Map<String, Object> buildSubStatus(int subStatusEnum) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("id", subStatusEnum);
String value = switch (subStatusEnum) {
case 0 -> "None";
case 100 -> "Inactive";
case 200 -> "Dormant";
case 300 -> "Block";
case 400 -> "Block Debit";
case 500 -> "Block Credit";
default -> "None";
};
s.put("code", "SavingsAccountSubStatusEnum." + value.replace(" ", ""));
s.put("value", value);
return s;
}
private Map<String, Object> buildAccountType(int accountTypeEnum) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("id", accountTypeEnum);
String value = switch (accountTypeEnum) {
case 1 -> "Individual";
case 2 -> "Group";
case 3 -> "JLG";
default -> "Individual";
};
s.put("code", "loanType." + value.toLowerCase());
s.put("value", value);
return s;
}
private Map<String, Object> buildTimeline(Map<String, Object> row) {
Map<String, Object> t = new LinkedHashMap<>();
t.put("submittedOnDate", row.get("submittedon_date"));
t.put("approvedOnDate", row.get("approvedon_date"));
t.put("activatedOnDate", row.get("activatedon_date"));
t.put("rejectedOnDate", row.get("rejectedon_date"));
t.put("withdrawnOnDate", row.get("withdrawnon_date"));
t.put("closedOnDate", row.get("closedon_date"));
return t;
}
private Map<String, Object> buildCurrency(Map<String, Object> row) {
Map<String, Object> c = new LinkedHashMap<>();
c.put("code", row.get("currency_code"));
c.put("name", row.get("currency_name"));
c.put("decimalPlaces", row.get("currency_digits"));
c.put("inMultiplesOf", row.get("currency_multiplesof"));
c.put("displaySymbol", row.get("currency_display_symbol"));
c.put("nameCode", row.get("currency_name_code"));
c.put("displayLabel", row.get("currency_name") + " [" + row.get("currency_code") + "]");
return c;
}
private Map<String, Object> buildSummary(Map<String, Object> row) {
Map<String, Object> s = new LinkedHashMap<>();
s.put("accountBalance", row.get("account_balance_derived"));
s.put("availableBalance", row.get("account_balance_derived"));
s.put("totalDeposits", row.get("total_deposits_derived"));
s.put("totalWithdrawals", row.get("total_withdrawals_derived"));
s.put("totalInterestEarned", row.get("total_interest_earned_derived"));
s.put("totalInterestPosted", row.get("total_interest_posted_derived"));
s.put("interestNotPosted", computeInterestNotPosted(row));
s.put("totalOverdraftInterestDerived", row.get("total_overdraft_interest_derived"));
s.put("totalWithholdTax", row.get("total_withhold_tax_derived"));
s.put("totalWithdrawalFees", null);
s.put("totalAnnualFees", null);
s.put("lastInterestCalculationDate", row.get("last_interest_calculation_date"));
s.put("onHoldFunds", row.get("on_hold_funds_derived"));
return s;
}
private BigDecimal computeInterestNotPosted(Map<String, Object> row) {
BigDecimal earned = toBigDecimal(row.get("total_interest_earned_derived"));
BigDecimal posted = toBigDecimal(row.get("total_interest_posted_derived"));
if (earned == null) return BigDecimal.ZERO;
if (posted == null) return earned;
return earned.subtract(posted);
}
private Map<String, Object> buildEnumOption(int id, String value) {
Map<String, Object> e = new LinkedHashMap<>();
e.put("id", id);
e.put("code", value.replace(" ", ""));
e.put("value", value);
return e;
}
private String interestCompoundingPeriodValue(int id) {
return switch (id) {
case 1 -> "Daily";
case 2 -> "Weekly";
case 3 -> "Bi-Weekly";
case 4 -> "Monthly";
case 5 -> "Quarterly";
case 6 -> "Semi-Annual";
case 7 -> "Annually";
default -> "Monthly";
};
}
private String interestPostingPeriodValue(int id) {
return switch (id) {
case 1 -> "Monthly";
case 2 -> "Quarterly";
case 3 -> "Biannual";
case 4 -> "Annually";
default -> "Annually";
};
}
private String interestCalcTypeValue(int id) {
return switch (id) {
case 1 -> "Daily Balance";
case 2 -> "Average Daily Balance";
default -> "Daily Balance";
};
}
private String daysInYearValue(int id) {
return switch (id) {
case 360 -> "360 Days";
case 365 -> "365 Days";
case 366 -> "Actual Days";
default -> "365 Days";
};
}
private int toInt(Object val, int defaultVal) {
if (val == null) return defaultVal;
if (val instanceof Number) return ((Number) val).intValue();
try { return Integer.parseInt(val.toString().trim()); } catch (Exception e) { return defaultVal; }
}
private BigDecimal toBigDecimal(Object val) {
if (val == null) return null;
if (val instanceof BigDecimal) return (BigDecimal) val;
try { return new BigDecimal(val.toString().trim()); } catch (Exception e) { return null; }
}
private static class FlatRowMapper implements RowMapper<Map<String, Object>> {
@Override
public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException {
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
Map<String, Object> row = new LinkedHashMap<>();
for (int i = 1; i <= cols; i++) {
row.put(meta.getColumnLabel(i), rs.getObject(i));
}
return row;
}
}
}
@@ -21,7 +21,9 @@ package org.apache.fineract.portfolio.savings.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -30,7 +32,6 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
@@ -64,12 +65,11 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult submitApplication(final JsonCommand command) {
context.authenticatedUser();
final Long clientId = command.longValueOfParameterNamed("clientId");
final Long productId = command.longValueOfParameterNamed("productId");
final Long officeId = command.parameterExists("officeId") ? command.longValueOfParameterNamed("officeId") : null;
final Long staffId = command.parameterExists("fieldOfficerId")
&& !isBlankString(command.parsedJson().getAsJsonObject(), "fieldOfficerId")
? command.longValueOfParameterNamed("fieldOfficerId") : null;
final JsonObject json = command.parsedJson().getAsJsonObject();
final Long clientId = getLong(json, "clientId");
final Long productId = getLong(json, "productId");
final Long officeId = getLong(json, "officeId");
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
final CurrentProduct product = currentProductRepository.findById(productId)
@@ -78,32 +78,29 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
? officeRepository.findOneWithNotFoundDetection(officeId)
: client.getOffice();
Staff staff = null;
// staff loaded lazily if needed — keep null for now if not in scope
final String dateFormat = getString(json, "dateFormat");
final String locale = getString(json, "locale");
LocalDate submittedOnDate = command.localDateValueOfParameterNamed("submittedOnDate");
LocalDate submittedOnDate = parseDate(getString(json, "submittedOnDate"), dateFormat, locale);
if (submittedOnDate == null) submittedOnDate = LocalDate.now();
// accountType may arrive as integer or string — store null if absent/unparseable
// accountType may arrive as integer or descriptive string — store null if unparseable
Integer accountTypeEnum = null;
if (command.parameterExists("accountType")) {
if (json.has("accountType") && !json.get("accountType").isJsonNull()) {
try {
accountTypeEnum = command.integerValueOfParameterNamed("accountType");
} catch (Exception ignored) {
// string value like "Standard Account" — ignore, store null
}
accountTypeEnum = json.get("accountType").getAsInt();
} catch (Exception ignored) { }
}
// Override from request if provided, else use product defaults
boolean msme = command.parameterExists("msme")
? command.booleanPrimitiveValueOfParameterNamed("msme") : product.isMsme();
boolean autoDebitFacility = command.parameterExists("autoDebitFacility")
? command.booleanPrimitiveValueOfParameterNamed("autoDebitFacility") : product.isAutoDebitFacility();
boolean balanceSwapOption = command.parameterExists("balanceSwapOption")
? command.booleanPrimitiveValueOfParameterNamed("balanceSwapOption") : product.isBalanceSwapOption();
boolean msme = json.has("msme") && !json.get("msme").isJsonNull()
? json.get("msme").getAsBoolean() : product.isMsme();
boolean autoDebitFacility = json.has("autoDebitFacility") && !json.get("autoDebitFacility").isJsonNull()
? json.get("autoDebitFacility").getAsBoolean() : product.isAutoDebitFacility();
boolean balanceSwapOption = json.has("balanceSwapOption") && !json.get("balanceSwapOption").isJsonNull()
? json.get("balanceSwapOption").getAsBoolean() : product.isBalanceSwapOption();
final CurrentAccount account = CurrentAccount.createNew(
client, productId, office, staff, "TEMP", submittedOnDate, accountTypeEnum,
client, productId, office, null, "TEMP", submittedOnDate, accountTypeEnum,
product.currency().getCode(), product.currency().getDigitsAfterDecimal(),
product.currency().getInMultiplesOf(),
product.nominalAnnualInterestRate(),
@@ -115,37 +112,38 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
product.getInterestCalculateSelector(), product.getAveragePeriod(),
product.getAvgBalanceCalcPeriod());
if (command.parameterExists("establishmentDate")) {
account.setEstablishmentDate(command.localDateValueOfParameterNamed("establishmentDate"));
if (json.has("establishmentDate") && !json.get("establishmentDate").isJsonNull()) {
account.setEstablishmentDate(parseDate(json.get("establishmentDate").getAsString(), dateFormat, locale));
}
if (command.parameterExists("startOperationDate")) {
account.setStartOperationDate(command.localDateValueOfParameterNamed("startOperationDate"));
if (json.has("startOperationDate") && !json.get("startOperationDate").isJsonNull()) {
account.setStartOperationDate(parseDate(json.get("startOperationDate").getAsString(), dateFormat, locale));
}
if (command.parameterExists("isNonIndividualClient")) {
account.setNonIndividualClient(command.booleanPrimitiveValueOfParameterNamed("isNonIndividualClient"));
if (json.has("isNonIndividualClient") && !json.get("isNonIndividualClient").isJsonNull()) {
account.setNonIndividualClient(json.get("isNonIndividualClient").getAsBoolean());
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
if (json.has("signatoryRights") && !json.get("signatoryRights").isJsonNull()) {
account.setSignatoryRights(json.get("signatoryRights").getAsString());
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
if (json.has("modeOfOperation") && !json.get("modeOfOperation").isJsonNull()) {
account.setModeOfOperation(json.get("modeOfOperation").getAsString());
}
if (command.parameterExists("introducerName")) {
account.setIntroducerName(command.stringValueOfParameterNamed("introducerName"));
if (json.has("introducerName") && !json.get("introducerName").isJsonNull()) {
account.setIntroducerName(json.get("introducerName").getAsString());
}
if (command.parameterExists("introducerClientId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerClientId"));
} else if (command.parameterExists("introducerId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerId"));
// UI may send "introducerClientId" or "introducerId"
Long introducerClientId = getLong(json, "introducerClientId");
if (introducerClientId == null) introducerClientId = getLong(json, "introducerId");
if (introducerClientId != null) account.setIntroducerClientId(introducerClientId);
if (json.has("externalId") && !json.get("externalId").isJsonNull()) {
account.setExternalId(json.get("externalId").getAsString());
}
if (command.parameterExists("externalId")) {
account.setExternalId(command.stringValueOfParameterNamed("externalId"));
if (json.has("accountStatus") && !json.get("accountStatus").isJsonNull()) {
account.setAccountStatus(json.get("accountStatus").getAsString());
}
if (command.parameterExists("accountStatus")) {
account.setAccountStatus(command.stringValueOfParameterNamed("accountStatus"));
}
if (command.parameterExists("customerSegment")) {
account.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment"));
// Override customerSegment from request if provided
if (json.has("customerSegment") && !json.get("customerSegment").isJsonNull()) {
account.setCustomerSegment(json.get("customerSegment").getAsString());
}
// Save first to get ID
@@ -156,19 +154,20 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
account.updateAccountNo(accountNo);
// Facility config — UI sends "accountFacilityConfig", internal uses "facilityConfig"
if (command.parameterExists("accountFacilityConfig") || command.parameterExists("facilityConfig")) {
final CurrentAccountFacilityConfig facilityConfig = assembleFacilityConfig(account, command);
account.setFacilityConfig(facilityConfig);
JsonObject fcJson = json.getAsJsonObject("accountFacilityConfig");
if (fcJson == null) fcJson = json.getAsJsonObject("facilityConfig");
if (fcJson != null) {
account.setFacilityConfig(assembleFacilityConfig(account, fcJson));
}
// Joint holders
if (command.parameterExists("jointHolders")) {
assembleJointHolders(account, command);
if (json.has("jointHolders") && json.get("jointHolders").isJsonArray()) {
assembleJointHolders(account, json.getAsJsonArray("jointHolders"));
}
// Partners (non-individual)
if (command.parameterExists("partners")) {
assemblePartners(account, command);
if (json.has("partners") && json.get("partners").isJsonArray()) {
assemblePartners(account, json.getAsJsonArray("partners"));
}
currentAccountRepository.saveAndFlush(account);
@@ -185,7 +184,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult approveApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate approvedOnDate = command.localDateValueOfParameterNamed("approvedOnDate");
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate approvedOnDate = parseDate(getString(json, "approvedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (approvedOnDate == null) approvedOnDate = LocalDate.now();
account.approve(approvedOnDate);
currentAccountRepository.saveAndFlush(account);
@@ -208,7 +208,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult activate(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate activatedOnDate = command.localDateValueOfParameterNamed("activatedOnDate");
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate activatedOnDate = parseDate(getString(json, "activatedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (activatedOnDate == null) activatedOnDate = LocalDate.now();
account.activate(activatedOnDate);
currentAccountRepository.saveAndFlush(account);
@@ -220,7 +221,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult rejectApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate rejectedOnDate = command.localDateValueOfParameterNamed("rejectedOnDate");
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate rejectedOnDate = parseDate(getString(json, "rejectedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (rejectedOnDate == null) rejectedOnDate = LocalDate.now();
account.reject(rejectedOnDate);
currentAccountRepository.saveAndFlush(account);
@@ -232,7 +234,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult withdrawApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate withdrawnOnDate = command.localDateValueOfParameterNamed("withdrawnOnDate");
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate withdrawnOnDate = parseDate(getString(json, "withdrawnOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (withdrawnOnDate == null) withdrawnOnDate = LocalDate.now();
account.withdraw(withdrawnOnDate);
currentAccountRepository.saveAndFlush(account);
@@ -244,7 +247,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult closeAccount(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate closedOnDate = command.localDateValueOfParameterNamed("closedOnDate");
final JsonObject json = command.parsedJson().getAsJsonObject();
LocalDate closedOnDate = parseDate(getString(json, "closedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
if (closedOnDate == null) closedOnDate = LocalDate.now();
account.close(closedOnDate);
currentAccountRepository.saveAndFlush(account);
@@ -256,14 +260,15 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
public CommandProcessingResult modifyApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
if (command.parameterExists("msme")) {
account.setMsme(command.booleanPrimitiveValueOfParameterNamed("msme"));
final JsonObject json = command.parsedJson().getAsJsonObject();
if (json.has("msme") && !json.get("msme").isJsonNull()) {
account.setMsme(json.get("msme").getAsBoolean());
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
if (json.has("signatoryRights") && !json.get("signatoryRights").isJsonNull()) {
account.setSignatoryRights(json.get("signatoryRights").getAsString());
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
if (json.has("modeOfOperation") && !json.get("modeOfOperation").isJsonNull()) {
account.setModeOfOperation(json.get("modeOfOperation").getAsString());
}
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
@@ -285,13 +290,8 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
.orElseThrow(() -> new SavingsAccountNotFoundException(accountId));
}
private CurrentAccountFacilityConfig assembleFacilityConfig(CurrentAccount account, JsonCommand command) {
private CurrentAccountFacilityConfig assembleFacilityConfig(CurrentAccount account, JsonObject fcJson) {
final CurrentAccountFacilityConfig cfg = CurrentAccountFacilityConfig.createFor(account);
final JsonObject root = command.parsedJson().getAsJsonObject();
JsonObject fcJson = root.getAsJsonObject("accountFacilityConfig");
if (fcJson == null) fcJson = root.getAsJsonObject("facilityConfig");
if (fcJson == null) return cfg;
cfg.setAtmFacility(getBool(fcJson, "atmFacility"));
cfg.setAtmWithdrawalFacility(getBool(fcJson, "atmWithdrawalFacility"));
cfg.setUpiFacility(getBool(fcJson, "upiFacility"));
@@ -313,58 +313,68 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
return cfg;
}
private void assembleJointHolders(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("jointHolders");
if (arr == null) return;
private void assembleJointHolders(CurrentAccount account, JsonArray arr) {
for (JsonElement el : arr) {
final JsonObject jo = el.getAsJsonObject();
final CurrentAccountJointHolder holder = new CurrentAccountJointHolder();
holder.setCurrentAccount(account);
if (jo.has("clientId") && !jo.get("clientId").isJsonNull()) {
holder.setClientId(jo.get("clientId").getAsLong());
}
if (jo.has("cifNumber") && !jo.get("cifNumber").isJsonNull()) {
holder.setCifNumber(jo.get("cifNumber").getAsString());
}
if (jo.has("displayName") && !jo.get("displayName").isJsonNull()) {
holder.setDisplayName(jo.get("displayName").getAsString());
}
if (jo.has("signingAuthority") && !jo.get("signingAuthority").isJsonNull()) {
holder.setSigningAuthority(jo.get("signingAuthority").getAsString());
}
if (jo.has("role") && !jo.get("role").isJsonNull()) {
holder.setRole(jo.get("role").getAsString());
}
Long cid = getLong(jo, "clientId");
if (cid != null) holder.setClientId(cid);
if (jo.has("cifNumber") && !jo.get("cifNumber").isJsonNull()) holder.setCifNumber(jo.get("cifNumber").getAsString());
if (jo.has("displayName") && !jo.get("displayName").isJsonNull()) holder.setDisplayName(jo.get("displayName").getAsString());
if (jo.has("signingAuthority") && !jo.get("signingAuthority").isJsonNull()) holder.setSigningAuthority(jo.get("signingAuthority").getAsString());
if (jo.has("role") && !jo.get("role").isJsonNull()) holder.setRole(jo.get("role").getAsString());
account.getJointHolders().add(holder);
}
}
private void assemblePartners(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("partners");
if (arr == null) return;
private void assemblePartners(CurrentAccount account, JsonArray arr) {
for (JsonElement el : arr) {
final JsonObject jo = el.getAsJsonObject();
final CurrentAccountPartner partner = new CurrentAccountPartner();
partner.setCurrentAccount(account);
if (jo.has("clientId") && !jo.get("clientId").isJsonNull()) {
partner.setClientId(jo.get("clientId").getAsLong());
}
if (jo.has("clientCif") && !jo.get("clientCif").isJsonNull()) {
partner.setClientCif(jo.get("clientCif").getAsString());
}
if (jo.has("clientName") && !jo.get("clientName").isJsonNull()) {
partner.setClientName(jo.get("clientName").getAsString());
}
Long cid = getLong(jo, "clientId");
if (cid != null) partner.setClientId(cid);
if (jo.has("clientCif") && !jo.get("clientCif").isJsonNull()) partner.setClientCif(jo.get("clientCif").getAsString());
if (jo.has("clientName") && !jo.get("clientName").isJsonNull()) partner.setClientName(jo.get("clientName").getAsString());
account.getPartners().add(partner);
}
}
private Long getLong(JsonObject obj, String key) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return null;
try {
String val = obj.get(key).getAsString().trim();
if (val.isEmpty()) return null;
return Long.parseLong(val);
} catch (Exception e) {
return null;
}
}
private String getString(JsonObject obj, String key) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return null;
String val = obj.get(key).getAsString().trim();
return val.isEmpty() ? null : val;
}
private boolean getBool(JsonObject obj, String key) {
return obj.has(key) && !obj.get(key).isJsonNull() && obj.get(key).getAsBoolean();
}
private boolean isBlankString(JsonObject obj, String key) {
if (!obj.has(key) || obj.get(key).isJsonNull()) return true;
return obj.get(key).getAsString().isBlank();
private LocalDate parseDate(String dateStr, String dateFormat, String locale) {
if (dateStr == null || dateStr.isBlank()) return null;
try {
String fmt = dateFormat != null ? dateFormat : "dd MMMM yyyy";
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern(fmt,
locale != null ? new java.util.Locale(locale) : java.util.Locale.ENGLISH));
} catch (Exception e) {
try {
return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("dd MMMM yyyy", java.util.Locale.ENGLISH));
} catch (Exception ex) {
log.warn("Could not parse date: {}", dateStr);
return null;
}
}
}
}
@@ -276,6 +276,13 @@
<include file="parts/0270_add_rd_product_extra_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0271_create_rd_base_interest_rate_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0272_seed_rd_base_interest_rate_data.xml" relativeToChangelogFile="true"/>
<include file="parts/0250_add_kyc_director_fields.xml" relativeToChangelogFile="true"/>
<include file="parts/0251_add_kyc_proof_type_codes.xml" relativeToChangelogFile="true"/>
<include file="parts/0252_add_ckyc_uploaded_on.xml" relativeToChangelogFile="true"/>
<include file="parts/0253_add_ckyc_batch_table.xml" relativeToChangelogFile="true"/>
<include file="parts/0254_fix_ckyc_batch_audit_columns.xml" relativeToChangelogFile="true"/>
<include file="parts/0255_add_ckyc_batch_id_to_client_kyc.xml" relativeToChangelogFile="true"/>
<include file="parts/0273_add_gl_account_id_to_current_product.xml" relativeToChangelogFile="true"/>
<include file="parts/0276_create_current_account_tables.xml" relativeToChangelogFile="true"/>
<include file="parts/0277_fix_current_account_audit_columns.xml" relativeToChangelogFile="true"/>
</databaseChangeLog>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
<!-- Rename audit columns in m_current_account to match AbstractAuditableCustom mappings -->
<changeSet id="0277_fix_current_account_audit_columns" author="system">
<renameColumn tableName="m_current_account" oldColumnName="created_by" newColumnName="createdby_id" columnDataType="bigint"/>
<renameColumn tableName="m_current_account" oldColumnName="last_modified_by" newColumnName="lastmodifiedby_id" columnDataType="bigint"/>
<renameColumn tableName="m_current_account" oldColumnName="last_modified_date" newColumnName="lastmodified_date" columnDataType="datetime"/>
</changeSet>
</databaseChangeLog>