current account saving account ,rd account code udpate

This commit is contained in:
Nitesh Yadav
2026-04-29 15:30:36 +05:30
42 changed files with 35014 additions and 96 deletions
@@ -36,10 +36,12 @@ import org.apache.fineract.portfolio.client.domain.ClientRepository;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
/**
@@ -69,6 +71,7 @@ public class AccountNumberGenerator {
private final SavingsAccountRepository savingsAccountRepository;
private final GLAccountRepository glAccountRepository;
private final SavingsProductRepository savingsProductRepository;
private final JdbcTemplate jdbcTemplate;
public String generate(Client client, AccountNumberFormat accountNumberFormat) {
Map<String, String> propertyMap = new HashMap<>();
@@ -119,6 +122,35 @@ public class AccountNumberGenerator {
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
/**
* Generate CBS account number for a current account.
* GL code is fetched directly from m_current_product using the current account's product_id.
* Format: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5) = 15 digits
*/
public String generateForCurrentAccount(CurrentAccount currentAccount) {
Long productId = currentAccount.getProductId();
Long glAccountId = jdbcTemplate.query(
"SELECT gl_account_id FROM m_current_product WHERE id = ?",
rs -> rs.next() ? rs.getLong("gl_account_id") : null,
productId);
String glCode = "000000";
if (glAccountId != null && glAccountId > 0) {
glCode = glAccountRepository.findById(glAccountId)
.map(gl -> StringUtils.leftPad(gl.getGlCode(), 6, '0'))
.orElse("000000");
}
glCode = glCode.length() > 6 ? glCode.substring(glCode.length() - 6) : StringUtils.leftPad(glCode, 6, '0');
String officeId = StringUtils.leftPad(currentAccount.getOffice().getId().toString(), 2, '0');
if (officeId.length() > 2) officeId = officeId.substring(officeId.length() - 2);
String accountId = StringUtils.leftPad(currentAccount.getId().toString(), 5, '0');
if (accountId.length() > 5) accountId = accountId.substring(accountId.length() - 5);
return CBS_FIXED_PREFIX + glCode + officeId + accountId;
}
public String previewAccountNumber(Long productId, Long officeId) {
String glCode = "000000";
if (productId != null) {
@@ -40,9 +40,6 @@ import java.util.Collection;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.commands.domain.CommandWrapper;
import org.apache.fineract.commands.service.CommandWrapperBuilder;
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
import org.apache.fineract.infrastructure.core.api.ApiParameterHelper;
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
@@ -59,6 +56,7 @@ 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.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;
@@ -75,10 +73,10 @@ public class CurrentAccountsApiResource {
private final CurrentAccountTemplateReadPlatformService currentAccountTemplateReadPlatformService;
private final PlatformSecurityContext context;
private final DefaultToApiJsonSerializer<SavingsAccountData> toApiJsonSerializer;
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
private final ApiRequestParameterHelper apiRequestParameterHelper;
private final SavingsAccountChargeReadPlatformService savingsAccountChargeReadPlatformService;
private final SqlValidator sqlValidator;
private final CurrentAccountWritePlatformService currentAccountWritePlatformService;
@GET
@Path("template")
@@ -128,8 +126,9 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Submit new current account application")
@ApiResponse(responseCode = "200", description = "OK")
public String submitApplication(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().createCurrentAccount().withJson(apiRequestBodyAsJson).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
final CommandProcessingResult result = currentAccountWritePlatformService.submitApplication(command);
return toApiJsonSerializer.serialize(result);
}
@@ -164,16 +163,9 @@ public class CurrentAccountsApiResource {
@Parameter(hidden = true) final String apiRequestBodyAsJson,
@QueryParam("command") final String commandParam) {
if (is(commandParam, "updateWithHoldTax")) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson)
.updateWithHoldTaxCurrentAccount(accountId).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
return toApiJsonSerializer.serialize(result);
}
final CommandWrapper commandRequest = new CommandWrapperBuilder().updateCurrentAccount(accountId).withJson(apiRequestBodyAsJson)
.build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(apiRequestBodyAsJson);
final CommandProcessingResult result = currentAccountWritePlatformService.modifyApplication(accountId, command);
return toApiJsonSerializer.serialize(result);
}
@@ -192,53 +184,27 @@ public class CurrentAccountsApiResource {
jsonApiRequest = "{}";
}
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(jsonApiRequest);
final org.apache.fineract.infrastructure.core.api.JsonCommand command =
org.apache.fineract.infrastructure.core.api.JsonCommand.from(jsonApiRequest);
CommandProcessingResult result = null;
if (is(commandParam, "reject")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.rejectCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.rejectApplication(accountId, command);
} else if (is(commandParam, "withdrawnByApplicant")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.withdrawCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.withdrawApplication(accountId, command);
} else if (is(commandParam, "approve")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.approveCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.approveApplication(accountId, command);
} else if (is(commandParam, "undoapproval")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.undoCurrentAccountApplication(accountId).build());
result = currentAccountWritePlatformService.undoApproval(accountId, command);
} else if (is(commandParam, "activate")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountActivation(accountId).build());
} else if (is(commandParam, "calculateInterest")) {
result = commandsSourceWritePlatformService
.logCommandSource(builder.withNoJsonBody().currentAccountInterestCalculation(accountId).build());
} else if (is(commandParam, "postInterest")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountInterestPosting(accountId).build());
} else if (is(commandParam, "applyAnnualFees")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.currentAccountApplyAnnualFees(accountId).build());
result = currentAccountWritePlatformService.activate(accountId, command);
} else if (is(commandParam, "close")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.closeCurrentAccountApplication(accountId).build());
} else if (is(commandParam, "assignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.assignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, "unassignSavingsOfficer")) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unassignCurrentAccountOfficer(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockDebitsFromCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCreditsToCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_BLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.blockCurrentAccount(accountId).build());
} else if (is(commandParam, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT)) {
result = commandsSourceWritePlatformService.logCommandSource(builder.unblockCurrentAccount(accountId).build());
result = currentAccountWritePlatformService.closeAccount(accountId, command);
}
if (result == null) {
throw new UnrecognizedQueryParamException("command", commandParam,
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "calculateInterest",
"postInterest", "close", "assignSavingsOfficer", "unassignSavingsOfficer",
SavingsApiConstants.COMMAND_BLOCK_DEBIT, SavingsApiConstants.COMMAND_UNBLOCK_DEBIT,
SavingsApiConstants.COMMAND_BLOCK_CREDIT, SavingsApiConstants.COMMAND_UNBLOCK_CREDIT,
SavingsApiConstants.COMMAND_BLOCK_ACCOUNT, SavingsApiConstants.COMMAND_UNBLOCK_ACCOUNT });
new Object[] { "reject", "withdrawnByApplicant", "approve", "undoapproval", "activate", "close" });
}
return toApiJsonSerializer.serialize(result);
@@ -251,8 +217,7 @@ public class CurrentAccountsApiResource {
@Operation(summary = "Delete a current account application")
@ApiResponse(responseCode = "200", description = "OK")
public String delete(@PathParam("accountId") final Long accountId) {
final CommandWrapper commandRequest = new CommandWrapperBuilder().deleteCurrentAccount(accountId).build();
final CommandProcessingResult result = commandsSourceWritePlatformService.logCommandSource(commandRequest);
final CommandProcessingResult result = currentAccountWritePlatformService.deleteApplication(accountId);
return toApiJsonSerializer.serialize(result);
}
@@ -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,114 @@ 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").isJsonNull()
? h.get("cifNumber").getAsString() : null;
final String displayName = h.has("displayName") && !h.get("displayName").isJsonNull()
? h.get("displayName").getAsString() : null;
final BigDecimal sharePercentage = h.has("sharePercentage") && !h.get("sharePercentage").isJsonNull()
? h.get("sharePercentage").getAsBigDecimal() : null;
final boolean isPrimary = h.has("isPrimary") && h.get("isPrimary").getAsBoolean();
String signingAuthority = h.has("signingAuthority") && !h.get("signingAuthority").isJsonNull()
? h.get("signingAuthority").getAsString() : null;
String role = h.has("role") && !h.get("role").isJsonNull()
? h.get("role").getAsString() : null;
if (signingAuthority == null || signingAuthority.trim().isEmpty()) {
signingAuthority = isPrimary ? "Mandatory" : "Optional";
}
if (role == null || role.trim().isEmpty()) {
role = isPrimary ? "Primary Account Holder" : "Co-Holder";
}
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);
@@ -26,7 +26,10 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.account.service.AccountNumberGenerator;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
import org.apache.fineract.infrastructure.dataqueries.data.DatatableData;
@@ -78,6 +81,7 @@ public class CurrentAccountTemplateReadPlatformServiceImpl implements CurrentAcc
private final ChargeReadPlatformService chargeReadPlatformService;
private final EntityDatatableChecksReadService entityDatatableChecksReadService;
private final AccountNumberGenerator accountNumberGenerator;
private final CodeValueReadPlatformService codeValueReadPlatformService;
public CurrentAccountTemplateReadPlatformServiceImpl(final PlatformSecurityContext context, final JdbcTemplate jdbcTemplate,
final ClientReadPlatformService clientReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
@@ -87,7 +91,8 @@ public class CurrentAccountTemplateReadPlatformServiceImpl implements CurrentAcc
final ChargeReadPlatformService chargeReadPlatformService,
final EntityDatatableChecksReadService entityDatatableChecksReadService,
final ColumnValidator columnValidator,
final AccountNumberGenerator accountNumberGenerator) {
final AccountNumberGenerator accountNumberGenerator,
final CodeValueReadPlatformService codeValueReadPlatformService) {
this.context = context;
this.jdbcTemplate = jdbcTemplate;
this.clientReadPlatformService = clientReadPlatformService;
@@ -98,6 +103,7 @@ public class CurrentAccountTemplateReadPlatformServiceImpl implements CurrentAcc
this.chargeReadPlatformService = chargeReadPlatformService;
this.entityDatatableChecksReadService = entityDatatableChecksReadService;
this.accountNumberGenerator = accountNumberGenerator;
this.codeValueReadPlatformService = codeValueReadPlatformService;
}
@Override
@@ -204,6 +210,12 @@ public class CurrentAccountTemplateReadPlatformServiceImpl implements CurrentAcc
// Classification fields
template.setAccountType(productData.getAccountType());
template.setCustomerSegment(productData.getCustomerSegment());
final Collection<CodeValueData> accountTypeOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.CURRENT_PRODUCT_ACCOUNT_TYPE);
final Collection<CodeValueData> customerSegmentOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.CURRENT_PRODUCT_CUSTOMER_SEGMENT);
template.setAccountTypeOptions(accountTypeOpts);
template.setCustomerSegmentOptions(customerSegmentOpts);
template.setProductCode(productData.getProductCode());
template.setEffectiveDate(productData.getEffectiveDate());
template.setEffectiveTo(productData.getEffectiveTo());
@@ -0,0 +1,370 @@
/**
* 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 com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.time.LocalDate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
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;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountPartner;
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountRepository;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWritePlatformService;
import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountNumberGenerator;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProduct;
import org.apache.fineract.portfolio.currentproduct.domain.CurrentProductRepository;
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Slf4j
@Service
@RequiredArgsConstructor
public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWritePlatformService {
private final PlatformSecurityContext context;
private final CurrentAccountRepository currentAccountRepository;
private final CurrentProductRepository currentProductRepository;
private final ClientRepositoryWrapper clientRepository;
private final OfficeRepositoryWrapper officeRepository;
private final CurrentAccountNumberGenerator accountNumberGenerator;
@Override
@Transactional
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 Client client = clientRepository.findOneWithNotFoundDetection(clientId);
final CurrentProduct product = currentProductRepository.findById(productId)
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
final Office office = officeId != null
? officeRepository.findOneWithNotFoundDetection(officeId)
: client.getOffice();
Staff staff = null;
// staff loaded lazily if needed — keep null for now if not in scope
LocalDate submittedOnDate = command.localDateValueOfParameterNamed("submittedOnDate");
if (submittedOnDate == null) submittedOnDate = LocalDate.now();
// accountType may arrive as integer or string — store null if absent/unparseable
Integer accountTypeEnum = null;
if (command.parameterExists("accountType")) {
try {
accountTypeEnum = command.integerValueOfParameterNamed("accountType");
} catch (Exception ignored) {
// string value like "Standard Account" — ignore, store null
}
}
// 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();
final CurrentAccount account = CurrentAccount.createNew(
client, productId, office, staff, "TEMP", submittedOnDate, accountTypeEnum,
product.currency().getCode(), product.currency().getDigitsAfterDecimal(),
product.currency().getInMultiplesOf(),
product.nominalAnnualInterestRate(),
product.getInterestCompoundingPeriodType(), product.getInterestPostingPeriodType(),
product.getInterestCalculationType(), product.getInterestCalculationDaysInYearType(),
product.isAllowOverdraft(), product.getOverdraftLimit(),
msme, autoDebitFacility, balanceSwapOption,
product.getCustomerSegment(), product.isAutoPost(),
product.getInterestCalculateSelector(), product.getAveragePeriod(),
product.getAvgBalanceCalcPeriod());
if (command.parameterExists("establishmentDate")) {
account.setEstablishmentDate(command.localDateValueOfParameterNamed("establishmentDate"));
}
if (command.parameterExists("startOperationDate")) {
account.setStartOperationDate(command.localDateValueOfParameterNamed("startOperationDate"));
}
if (command.parameterExists("isNonIndividualClient")) {
account.setNonIndividualClient(command.booleanPrimitiveValueOfParameterNamed("isNonIndividualClient"));
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
}
if (command.parameterExists("introducerName")) {
account.setIntroducerName(command.stringValueOfParameterNamed("introducerName"));
}
if (command.parameterExists("introducerClientId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerClientId"));
} else if (command.parameterExists("introducerId")) {
account.setIntroducerClientId(command.longValueOfParameterNamed("introducerId"));
}
if (command.parameterExists("externalId")) {
account.setExternalId(command.stringValueOfParameterNamed("externalId"));
}
if (command.parameterExists("accountStatus")) {
account.setAccountStatus(command.stringValueOfParameterNamed("accountStatus"));
}
if (command.parameterExists("customerSegment")) {
account.setCustomerSegment(command.stringValueOfParameterNamed("customerSegment"));
}
// Save first to get ID
currentAccountRepository.saveAndFlush(account);
// Generate account number: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5)
final String accountNo = accountNumberGenerator.generate(account);
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);
}
// Joint holders
if (command.parameterExists("jointHolders")) {
assembleJointHolders(account, command);
}
// Partners (non-individual)
if (command.parameterExists("partners")) {
assemblePartners(account, command);
}
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder()
.withEntityId(account.getId())
.withClientId(clientId)
.withOfficeId(office.getId())
.build();
}
@Override
@Transactional
public CommandProcessingResult approveApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate approvedOnDate = command.localDateValueOfParameterNamed("approvedOnDate");
if (approvedOnDate == null) approvedOnDate = LocalDate.now();
account.approve(approvedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult undoApproval(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
account.setStatusEnum(100);
account.setApprovedOnDate(null);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult activate(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate activatedOnDate = command.localDateValueOfParameterNamed("activatedOnDate");
if (activatedOnDate == null) activatedOnDate = LocalDate.now();
account.activate(activatedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult rejectApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate rejectedOnDate = command.localDateValueOfParameterNamed("rejectedOnDate");
if (rejectedOnDate == null) rejectedOnDate = LocalDate.now();
account.reject(rejectedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult withdrawApplication(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate withdrawnOnDate = command.localDateValueOfParameterNamed("withdrawnOnDate");
if (withdrawnOnDate == null) withdrawnOnDate = LocalDate.now();
account.withdraw(withdrawnOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult closeAccount(final Long accountId, final JsonCommand command) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
LocalDate closedOnDate = command.localDateValueOfParameterNamed("closedOnDate");
if (closedOnDate == null) closedOnDate = LocalDate.now();
account.close(closedOnDate);
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
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"));
}
if (command.parameterExists("signatoryRights")) {
account.setSignatoryRights(command.stringValueOfParameterNamed("signatoryRights"));
}
if (command.parameterExists("modeOfOperation")) {
account.setModeOfOperation(command.stringValueOfParameterNamed("modeOfOperation"));
}
currentAccountRepository.saveAndFlush(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
@Override
@Transactional
public CommandProcessingResult deleteApplication(final Long accountId) {
context.authenticatedUser();
final CurrentAccount account = findAccount(accountId);
currentAccountRepository.delete(account);
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
}
// ─── helpers ──────────────────────────────────────────────────────────────
private CurrentAccount findAccount(Long accountId) {
return currentAccountRepository.findById(accountId)
.orElseThrow(() -> new SavingsAccountNotFoundException(accountId));
}
private CurrentAccountFacilityConfig assembleFacilityConfig(CurrentAccount account, JsonCommand command) {
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"));
cfg.setPosTransactionsFacility(getBool(fcJson, "posTransactionsFacility"));
cfg.setChequeBook(getBool(fcJson, "chequeBook"));
cfg.setImpsFacility(getBool(fcJson, "impsFacility"));
cfg.setSmsAlerts(getBool(fcJson, "smsAlerts"));
cfg.setBbpsFacility(getBool(fcJson, "bbpsFacility"));
cfg.setInsuranceCover(getBool(fcJson, "insuranceCover"));
cfg.setBepgFacility(getBool(fcJson, "bepgFacility"));
cfg.setNeftFacility(getBool(fcJson, "neftFacility"));
cfg.setRtgsFacility(getBool(fcJson, "rtgsFacility"));
cfg.setInterBankTotalCapFacility(getBool(fcJson, "interBankTotalCapFacility"));
cfg.setWithdrawalFacility(getBool(fcJson, "withdrawalFacility"));
cfg.setDepositFacilityKyc(getBool(fcJson, "depositFacilityKyc"));
cfg.setVirtualDebitCard(getBool(fcJson, "virtualDebitCard"));
cfg.setInternetBanking(getBool(fcJson, "internetBanking"));
cfg.setMobileBanking(getBool(fcJson, "mobileBanking"));
return cfg;
}
private void assembleJointHolders(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("jointHolders");
if (arr == null) return;
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());
}
account.getJointHolders().add(holder);
}
}
private void assemblePartners(CurrentAccount account, JsonCommand command) {
final JsonArray arr = command.arrayOfParameterNamed("partners");
if (arr == null) return;
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());
}
account.getPartners().add(partner);
}
}
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();
}
}
@@ -32,6 +32,8 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.data.PaginationParameters;
import org.apache.fineract.infrastructure.core.data.PaginationParametersDataValidator;
@@ -70,6 +72,7 @@ import org.apache.fineract.portfolio.interestratechart.service.InterestRateChart
import org.apache.fineract.portfolio.paymentdetail.data.PaymentDetailData;
import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadService;
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
import org.apache.fineract.portfolio.savings.DepositAccountOnClosureType;
import org.apache.fineract.portfolio.savings.DepositAccountType;
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
@@ -132,6 +135,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
private final CalendarReadPlatformService calendarReadPlatformService;
private final PaymentTypeReadService paymentTypeReadPlatformService;
private final AccountNumberGenerator accountNumberGenerator;
private final CodeValueReadPlatformService codeValueReadPlatformService;
@Override
public Collection<DepositAccountData> retrieveAll(final DepositAccountType depositAccountType,
@@ -412,6 +416,18 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
// Re-apply CBS product fields lost during factory method object creation
((RecurringDepositAccountData) template).copyCbsFieldsFrom(rdTemplateWithCbs);
// Populate account type / customer segment options from code values
try {
final Collection<CodeValueData> accountTypeOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_ACCOUNT_TYPE);
final Collection<CodeValueData> customerSegmentOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.RD_PRODUCT_CUSTOMER_SEGMENT);
((RecurringDepositAccountData) template).setAccountTypeOptions(accountTypeOpts);
((RecurringDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts);
} catch (final Exception ex) {
log.warn("Failed to load RD account type/customer segment options: {}", ex.getMessage());
}
// Generate preview account number
try {
final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId);
@@ -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;
}
}
@@ -27,6 +27,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Locale;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.JdbcSupport;
@@ -194,6 +195,12 @@ public class SavingsAccountTemplateReadPlatformServiceImpl implements SavingsAcc
final SavingsProductData fullProduct = this.savingsProductReadPlatformService.retrieveOne(productId);
template.setAccountType(fullProduct.getAccountType());
template.setCustomerSegment(fullProduct.getCustomerSegment());
final Collection<CodeValueData> accountTypeOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_ACCOUNT_TYPE);
final Collection<CodeValueData> customerSegmentOpts = this.codeValueReadPlatformService
.retrieveCodeValuesByCode(SavingsApiConstants.SAVINGS_PRODUCT_CUSTOMER_SEGMENT);
template.setAccountTypeOptions(accountTypeOpts);
template.setCustomerSegmentOptions(customerSegmentOpts);
template.setProductCode(fullProduct.getProductCode());
template.setEffectiveDate(fullProduct.getEffectiveDate());
template.setEffectiveTo(fullProduct.getEffectiveTo());
@@ -228,12 +228,12 @@ public class SavingsConfiguration {
StaffReadPlatformService staffReadPlatformService, DepositsDropdownReadPlatformService depositsDropdownReadPlatformService,
SavingsAccountReadPlatformService savingsAccountReadPlatformService, DropdownReadPlatformService dropdownReadPlatformService,
CalendarReadPlatformService calendarReadPlatformService, PaymentTypeReadService paymentTypeReadPlatformService,
AccountNumberGenerator accountNumberGenerator) {
AccountNumberGenerator accountNumberGenerator, CodeValueReadPlatformService codeValueReadPlatformService) {
return new DepositAccountReadPlatformServiceImpl(context, jdbcTemplate, chartReadPlatformService, productChartReadPlatformService,
paginationParametersDataValidator, sqlGenerator, paginationHelper, clientReadPlatformService, groupReadPlatformService,
depositProductReadPlatformService, savingsDropdownReadPlatformService, chargeReadPlatformService, staffReadPlatformService,
depositsDropdownReadPlatformService, savingsAccountReadPlatformService, dropdownReadPlatformService,
calendarReadPlatformService, paymentTypeReadPlatformService, accountNumberGenerator);
calendarReadPlatformService, paymentTypeReadPlatformService, accountNumberGenerator, codeValueReadPlatformService);
}
@Bean
@@ -385,10 +385,11 @@ public class SavingsConfiguration {
GroupReadPlatformService groupReadPlatformService, CurrentProductReadPlatformService currentProductReadPlatformService,
StaffReadPlatformService staffReadPlatformService, SavingsDropdownReadPlatformService dropdownReadPlatformService,
ChargeReadPlatformService chargeReadPlatformService, EntityDatatableChecksReadService entityDatatableChecksReadService,
ColumnValidator columnValidator, AccountNumberGenerator accountNumberGenerator) {
ColumnValidator columnValidator, AccountNumberGenerator accountNumberGenerator,
CodeValueReadPlatformService codeValueReadPlatformService) {
return new CurrentAccountTemplateReadPlatformServiceImpl(context, jdbcTemplate, clientReadPlatformService, groupReadPlatformService,
currentProductReadPlatformService, staffReadPlatformService, dropdownReadPlatformService, chargeReadPlatformService,
entityDatatableChecksReadService, columnValidator, accountNumberGenerator);
entityDatatableChecksReadService, columnValidator, accountNumberGenerator, codeValueReadPlatformService);
}
@Bean
@@ -283,4 +283,5 @@
<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"/>
</databaseChangeLog>
@@ -5,6 +5,7 @@
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
<changeSet id="0272_insert_rd_base_rate_version" author="fineract">
<validCheckSum>9:4cb4d062d4ee9464fe5a4e32023580c2</validCheckSum>
<preConditions onFail="MARK_RAN">
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_rd_base_interest_rate_effective</sqlCheck>
</preConditions>
@@ -0,0 +1,393 @@
<?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">
<!-- =========================================================
m_current_account — main current account table
(fully separate from m_savings_account)
========================================================= -->
<changeSet id="0276_create_current_account_tables" author="system">
<createTable tableName="m_current_account">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="account_no" type="varchar(20)">
<constraints nullable="false" unique="true" uniqueConstraintName="ca_unq_account_no"/>
</column>
<column name="external_id" type="varchar(100)">
<constraints nullable="true" unique="true" uniqueConstraintName="ca_unq_external_id"/>
</column>
<!-- Ownership -->
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="group_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_group" referencedTableName="m_group" referencedColumnNames="id"/>
</column>
<column name="product_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_ca_product" referencedTableName="m_current_product" referencedColumnNames="id"/>
</column>
<column name="office_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_ca_office" referencedTableName="m_office" referencedColumnNames="id"/>
</column>
<column name="staff_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_staff" referencedTableName="m_staff" referencedColumnNames="id"/>
</column>
<!-- Status -->
<column name="status_enum" type="smallint" defaultValue="100">
<constraints nullable="false"/>
</column>
<!-- 100=Submitted, 200=Approved, 300=Active, 400=Withdrawn, 500=Rejected, 600=Closed -->
<column name="sub_status_enum" type="smallint" defaultValue="0">
<constraints nullable="false"/>
</column>
<!-- 0=None, 100=Inactive, 200=Dormant, 300=Escheat, 400=Block, 500=BlockDebit, 600=BlockCredit -->
<column name="account_type_enum" type="smallint" defaultValue="1">
<constraints nullable="false"/>
</column>
<!-- 1=Individual, 2=Group, 3=JLG -->
<!-- Lifecycle dates -->
<column name="submittedon_date" type="date"><constraints nullable="true"/></column>
<column name="submittedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_submit_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="approvedon_date" type="date"><constraints nullable="true"/></column>
<column name="approvedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_approve_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="activatedon_date" type="date"><constraints nullable="true"/></column>
<column name="activatedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_activate_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="rejectedon_date" type="date"><constraints nullable="true"/></column>
<column name="rejectedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_reject_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="withdrawnon_date" type="date"><constraints nullable="true"/></column>
<column name="withdrawnon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_withdraw_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<column name="closedon_date" type="date"><constraints nullable="true"/></column>
<column name="closedon_userid" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_close_user" referencedTableName="m_appuser" referencedColumnNames="id"/>
</column>
<!-- Currency (copied from product at account creation) -->
<column name="currency_code" type="varchar(3)"><constraints nullable="false"/></column>
<column name="currency_digits" type="smallint"><constraints nullable="false"/></column>
<column name="currency_multiplesof" type="smallint"><constraints nullable="true"/></column>
<!-- Interest settings (copied from product) -->
<column name="nominal_annual_interest_rate" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="interest_compounding_period_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_posting_period_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_calculation_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="interest_calculation_days_in_year_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="min_balance_for_interest_calculation" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="start_interest_calculation_date" type="date"><constraints nullable="true"/></column>
<column name="accrued_till_date" type="date"><constraints nullable="true"/></column>
<column name="last_interest_calculation_date" type="date"><constraints nullable="true"/></column>
<column name="last_closed_business_date" type="date"><constraints nullable="true"/></column>
<!-- Tax -->
<column name="withhold_tax" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="tax_group_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_ca_tax_group" referencedTableName="m_tax_group" referencedColumnNames="id"/>
</column>
<!-- Balance rules (copied from product) -->
<column name="min_required_opening_balance" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="lockin_period_frequency" type="smallint"><constraints nullable="true"/></column>
<column name="lockin_period_frequency_enum" type="smallint"><constraints nullable="true"/></column>
<column name="lockedin_until_date_derived" type="date"><constraints nullable="true"/></column>
<column name="enforce_min_required_balance" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="min_required_balance" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Overdraft (copied from product) -->
<column name="allow_overdraft" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="overdraft_limit" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="nominal_annual_interest_rate_overdraft" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="min_overdraft_for_interest_calculation" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Lien -->
<column name="is_lien_allowed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="max_allowed_lien_limit" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="on_hold_funds_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<!-- Running balance summary -->
<column name="account_balance_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_deposits_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_withdrawals_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_interest_earned_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_interest_posted_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_overdraft_interest_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_withhold_tax_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="total_savings_amount_on_hold" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="withdrawal_fee_for_transfer" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<!-- Block reason -->
<column name="reason_for_block" type="varchar(500)"><constraints nullable="true"/></column>
<!-- Current-account-specific fields -->
<column name="msme" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="auto_debit_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="balance_swap_option" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="establishment_date" type="date"><constraints nullable="true"/></column>
<column name="start_operation_date" type="date"><constraints nullable="true"/></column>
<column name="is_non_individual_client" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="signatory_rights" type="varchar(100)"><constraints nullable="true"/></column>
<!-- CBS account type / segment -->
<column name="cbs_account_type" type="varchar(100)"><constraints nullable="true"/></column>
<column name="customer_segment" type="varchar(100)"><constraints nullable="true"/></column>
<column name="account_status" type="varchar(50)"><constraints nullable="true"/></column>
<column name="mode_of_operation" type="varchar(50)"><constraints nullable="true"/></column>
<column name="special_category" type="varchar(100)"><constraints nullable="true"/></column>
<!-- Introducer -->
<column name="introducer_client_id" type="bigint"><constraints nullable="true"/></column>
<column name="introducer_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="introducer_cif_number" type="varchar(50)"><constraints nullable="true"/></column>
<!-- Nominee (JSON) -->
<column name="nominee_data" type="text"><constraints nullable="true"/></column>
<!-- Interest calculation settings from product -->
<column name="interest_calculate_selector" type="smallint"><constraints nullable="true"/></column>
<column name="average_period" type="varchar(20)"><constraints nullable="true"/></column>
<column name="auto_post" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="frequency_fix_day" type="smallint"><constraints nullable="true"/></column>
<column name="enable_lockin_period" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="balance_frequency" type="varchar(50)"><constraints nullable="true"/></column>
<column name="avg_balance_calc_period" type="varchar(10)"><constraints nullable="true"/></column>
<!-- Audit -->
<column name="created_by" type="bigint"><constraints nullable="true"/></column>
<column name="created_date" type="datetime"><constraints nullable="true"/></column>
<column name="last_modified_by" type="bigint"><constraints nullable="true"/></column>
<column name="last_modified_date" type="datetime"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_transaction
========================================================= -->
<createTable tableName="m_current_account_transaction">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cat_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="office_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cat_office" referencedTableName="m_office" referencedColumnNames="id"/>
</column>
<column name="payment_detail_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cat_payment" referencedTableName="m_payment_detail" referencedColumnNames="id"/>
</column>
<!-- Type: 1=Deposit, 2=Withdrawal, 3=Interest, 4=Charge, 5=ChargeWaiver, 6=Transfer, 7=Reversal -->
<column name="transaction_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="transaction_date" type="date"><constraints nullable="false"/></column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="ref_no" type="varchar(100)"><constraints nullable="true"/></column>
<column name="remarks" type="varchar(500)"><constraints nullable="true"/></column>
<!-- State -->
<column name="is_reversed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_reversal" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="original_transaction_id" type="bigint"><constraints nullable="true"/></column>
<column name="is_manual" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<!-- Balance -->
<column name="running_balance_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="cumulative_balance_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="balance_end_date_derived" type="date"><constraints nullable="true"/></column>
<column name="balance_number_of_days_derived" type="integer"><constraints nullable="true"/></column>
<column name="overdraft_amount_derived" type="decimal(19,6)"><constraints nullable="true"/></column>
<!-- Lien / Hold -->
<column name="is_lien_transaction" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="release_id_of_hold_amount_transaction" type="bigint"><constraints nullable="true"/></column>
<column name="reason_for_block" type="varchar(200)"><constraints nullable="true"/></column>
<column name="submitted_on_date" type="date"><constraints nullable="true"/></column>
<column name="created_date" type="datetime"><constraints nullable="true"/></column>
<column name="created_by" type="bigint"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_charge
========================================================= -->
<createTable tableName="m_current_account_charge">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cac_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="charge_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cac_charge" referencedTableName="m_charge" referencedColumnNames="id"/>
</column>
<column name="charge_time_enum" type="smallint"><constraints nullable="false"/></column>
<column name="charge_due_date" type="date"><constraints nullable="true"/></column>
<column name="fee_on_month" type="smallint"><constraints nullable="true"/></column>
<column name="fee_on_day" type="smallint"><constraints nullable="true"/></column>
<column name="fee_interval" type="smallint"><constraints nullable="true"/></column>
<column name="charge_calculation_enum" type="smallint"><constraints nullable="false"/></column>
<column name="calculation_percentage" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="calculation_on_amount" type="decimal(19,6)"><constraints nullable="true"/></column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="amount_paid_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_waived_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_writtenoff_derived" type="decimal(19,6)" defaultValue="0"><constraints nullable="false"/></column>
<column name="amount_outstanding_derived" type="decimal(19,6)"><constraints nullable="false"/></column>
<column name="is_paid_derived" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="waived" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_penalty" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="is_active" type="boolean" defaultValueBoolean="true"><constraints nullable="false"/></column>
<column name="inactivated_on_date" type="date"><constraints nullable="true"/></column>
<column name="free_withdrawal_count" type="integer"><constraints nullable="true"/></column>
<column name="charge_reset_date" type="date"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_facility_config
ATM / NEFT / RTGS / Cheque Book etc.
========================================================= -->
<createTable tableName="m_current_account_facility_config">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" unique="true" uniqueConstraintName="ca_fac_unq_account"
foreignKeyName="fk_cafac_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="atm_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="atm_withdrawal_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="upi_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="pos_transactions_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="cheque_book" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="imps_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="sms_alerts" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="bbps_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="insurance_cover" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="bepg_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="neft_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="rtgs_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="inter_bank_total_cap_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="withdrawal_facility" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="deposit_facility_kyc" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="virtual_debit_card" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="internet_banking" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
<column name="mobile_banking" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_joint_holder
Joint account holders
========================================================= -->
<createTable tableName="m_current_account_joint_holder">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cajh_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cajh_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="cif_number" type="varchar(50)"><constraints nullable="true"/></column>
<column name="display_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="share_percentage" type="decimal(5,2)"><constraints nullable="true"/></column>
<column name="signing_authority" type="varchar(100)"><constraints nullable="true"/></column>
<column name="role" type="varchar(100)"><constraints nullable="true"/></column>
<column name="is_primary" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_partner
Partners for non-individual (business) current accounts
========================================================= -->
<createTable tableName="m_current_account_partner">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_cap_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="client_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_cap_client" referencedTableName="m_client" referencedColumnNames="id"/>
</column>
<column name="client_cif" type="varchar(50)"><constraints nullable="true"/></column>
<column name="client_name" type="varchar(200)"><constraints nullable="true"/></column>
<column name="share_percentage" type="decimal(5,2)"><constraints nullable="true"/></column>
<column name="is_mandatory" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- =========================================================
m_current_account_officer_history
Officer assignment history
========================================================= -->
<createTable tableName="m_current_account_officer_history">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_caoh_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="staff_id" type="bigint">
<constraints nullable="true" foreignKeyName="fk_caoh_staff" referencedTableName="m_staff" referencedColumnNames="id"/>
</column>
<column name="start_date" type="date"><constraints nullable="false"/></column>
<column name="end_date" type="date"><constraints nullable="true"/></column>
<column name="created_by_id" type="bigint"><constraints nullable="true"/></column>
</createTable>
<!-- =========================================================
m_current_account_on_hold_transaction
On-hold / lien funds
========================================================= -->
<createTable tableName="m_current_account_on_hold_transaction">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="current_account_id" type="bigint">
<constraints nullable="false" foreignKeyName="fk_caoht_account" referencedTableName="m_current_account" referencedColumnNames="id"/>
</column>
<column name="amount" type="decimal(19,6)"><constraints nullable="false"/></column>
<!-- 1=Hold, 2=Release -->
<column name="transaction_type_enum" type="smallint"><constraints nullable="false"/></column>
<column name="transaction_date" type="date"><constraints nullable="false"/></column>
<column name="is_reversed" type="boolean" defaultValueBoolean="false"><constraints nullable="false"/></column>
</createTable>
<!-- Indexes for fast lookup -->
<createIndex indexName="idx_ca_client_id" tableName="m_current_account">
<column name="client_id"/>
</createIndex>
<createIndex indexName="idx_ca_product_id" tableName="m_current_account">
<column name="product_id"/>
</createIndex>
<createIndex indexName="idx_ca_status" tableName="m_current_account">
<column name="status_enum"/>
</createIndex>
<createIndex indexName="idx_cat_account_id" tableName="m_current_account_transaction">
<column name="current_account_id"/>
</createIndex>
<createIndex indexName="idx_cat_date" tableName="m_current_account_transaction">
<column name="transaction_date"/>
</createIndex>
</changeSet>
</databaseChangeLog>
@@ -151,6 +151,15 @@
<class>org.apache.fineract.portfolio.self.pockets.domain.PocketAccountMapping</class>
<class>org.apache.fineract.portfolio.self.registration.domain.SelfServiceRegistration</class>
<!-- Current Account Entities -->
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountPartner</class>
<class>org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountOfficerHistory</class>
<!-- Charge Module Entities -->
<class>org.apache.fineract.portfolio.charge.domain.Charge</class>
@@ -263,6 +272,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>
@@ -30,6 +30,7 @@ import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationC
import org.apache.fineract.infrastructure.configuration.data.GlobalConfigurationPropertyData;
import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService;
import org.apache.fineract.organisation.office.domain.Office;
import org.apache.fineract.accounting.glaccount.domain.GLAccountRepository;
import org.apache.fineract.portfolio.account.service.AccountNumberGenerator;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepository;
@@ -39,6 +40,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository;
import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct;
import org.junit.jupiter.api.BeforeEach;
@@ -50,6 +52,8 @@ public class AccountNumberGeneratorTest {
private ClientRepository clientRepo;
private LoanRepository loanRepo;
private SavingsAccountRepository savingsRepo;
private GLAccountRepository glAccountRepo;
private SavingsProductRepository savingsProductRepo;
private AccountNumberGenerator generator;
@@ -59,8 +63,10 @@ public class AccountNumberGeneratorTest {
clientRepo = mock(ClientRepository.class);
loanRepo = mock(LoanRepository.class);
savingsRepo = mock(SavingsAccountRepository.class);
glAccountRepo = mock(GLAccountRepository.class);
savingsProductRepo = mock(SavingsProductRepository.class);
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo);
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo);
GlobalConfigurationPropertyData accountLengthConfig = mock(GlobalConfigurationPropertyData.class);
when(accountLengthConfig.getValue()).thenReturn(Long.valueOf("9"));