Merge remote-tracking branch 'self-hosted/feature/account-charges' into feature/Backend-DSC-MFA
# Conflicts: # fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
This commit is contained in:
+52
@@ -44,6 +44,7 @@ import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand;
|
||||
import org.apache.fineract.accounting.journalentry.data.GLAccountLedgerReportData;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
||||
import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
|
||||
@@ -268,6 +269,57 @@ public class JournalEntriesApiResource {
|
||||
return this.apiJsonSerializerService.serialize(settings, officeOpeningBalancesData);
|
||||
}
|
||||
|
||||
/**
|
||||
* CBS GL Account Ledger Report — returns opening balance, day-wise summary,
|
||||
* period totals, and paginated entries for a given GL account and date range.
|
||||
*
|
||||
* GET /journalentries/ledger?glAccountId=5&fromDate=01 Jan 2025&toDate=31 Jan 2025&dateFormat=dd MMM yyyy&locale=en
|
||||
*/
|
||||
@GET
|
||||
@Path("ledger")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "GL Account Ledger Report",
|
||||
description = "Returns opening balance, day-wise running balance summary, period totals, and paginated journal entries for a GL account.")
|
||||
public String retrieveGLAccountLedger(@Context final UriInfo uriInfo,
|
||||
@QueryParam("glAccountId") @Parameter(description = "glAccountId") final Long glAccountId,
|
||||
@QueryParam("officeId") @Parameter(description = "officeId") final Long officeId,
|
||||
@QueryParam("manualEntriesOnly") @Parameter(description = "manualEntriesOnly") final Boolean onlyManualEntries,
|
||||
@QueryParam("fromDate") @Parameter(description = "fromDate") final DateParam fromDateParam,
|
||||
@QueryParam("toDate") @Parameter(description = "toDate") final DateParam toDateParam,
|
||||
@QueryParam("transactionId") @Parameter(description = "transactionId") final String transactionId,
|
||||
@QueryParam("offset") @Parameter(description = "offset") final Integer offset,
|
||||
@QueryParam("limit") @Parameter(description = "limit") final Integer limit,
|
||||
@QueryParam("orderBy") @Parameter(description = "orderBy") final String orderBy,
|
||||
@QueryParam("sortOrder") @Parameter(description = "sortOrder") final String sortOrder,
|
||||
@QueryParam("locale") @Parameter(description = "locale") final String locale,
|
||||
@QueryParam("dateFormat") @Parameter(description = "dateFormat") final String rawDateFormat) {
|
||||
|
||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSION);
|
||||
|
||||
final DateFormat dateFormat = StringUtils.isBlank(rawDateFormat) ? null : new DateFormat(rawDateFormat);
|
||||
LocalDate fromDate = null;
|
||||
if (fromDateParam != null) {
|
||||
fromDate = fromDateParam.getDate("fromDate", dateFormat, locale);
|
||||
}
|
||||
LocalDate toDate = null;
|
||||
if (toDateParam != null) {
|
||||
toDate = toDateParam.getDate("toDate", dateFormat, locale);
|
||||
}
|
||||
|
||||
sqlValidator.validate(orderBy);
|
||||
sqlValidator.validate(sortOrder);
|
||||
final SearchParameters searchParameters = SearchParameters.builder()
|
||||
.limit(limit).officeId(officeId).offset(offset)
|
||||
.orderBy(orderBy).sortOrder(sortOrder).build();
|
||||
|
||||
final GLAccountLedgerReportData ledgerData = this.journalEntryReadPlatformService
|
||||
.retrieveGLAccountLedger(glAccountId, officeId, onlyManualEntries, fromDate, toDate, transactionId, searchParameters);
|
||||
|
||||
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.apiJsonSerializerService.serialize(settings, ledgerData);
|
||||
}
|
||||
|
||||
private boolean is(final String commandParam, final String commandValue) {
|
||||
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
|
||||
}
|
||||
|
||||
+228
-12
@@ -24,9 +24,11 @@ import java.math.BigDecimal;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@@ -36,6 +38,8 @@ import org.apache.fineract.accounting.financialactivityaccount.domain.FinancialA
|
||||
import org.apache.fineract.accounting.glaccount.data.GLAccountData;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccountType;
|
||||
import org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformService;
|
||||
import org.apache.fineract.accounting.journalentry.data.DayWiseSummaryData;
|
||||
import org.apache.fineract.accounting.journalentry.data.GLAccountLedgerReportData;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryAssociationParametersData;
|
||||
import org.apache.fineract.accounting.journalentry.data.JournalEntryData;
|
||||
import org.apache.fineract.accounting.journalentry.data.OfficeOpeningBalancesData;
|
||||
@@ -97,7 +101,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
.append(" journalEntry.type_enum as entryType,journalEntry.amount as amount, journalEntry.transaction_id as transactionId,")
|
||||
.append(" journalEntry.entity_type_enum as entityType, journalEntry.entity_id as entityId, creatingUser.id as createdByUserId, ")
|
||||
.append(" creatingUser.username as createdByUserName, journalEntry.description as comments, ")
|
||||
.append(" journalEntry.submitted_on_date as submittedOnDate, journalEntry.reversed as reversed, ")
|
||||
.append(" journalEntry.created_on_utc as submittedOnDate, journalEntry.reversed as reversed, ")
|
||||
.append(" journalEntry.currency_code as currencyCode, curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, ")
|
||||
.append(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
|
||||
.append(" eao.external_id as externalAssetOwner ");
|
||||
@@ -157,7 +161,7 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
|
||||
final Long entityId = JdbcSupport.getLong(rs, "entityId");
|
||||
final Long createdByUserId = rs.getLong("createdByUserId");
|
||||
final LocalDate submittedOnDate = JdbcSupport.getLocalDate(rs, "submittedOnDate");
|
||||
final OffsetDateTime submittedOnDate = rs.getObject("submittedOnDate", OffsetDateTime.class);
|
||||
final String createdByUserName = rs.getString("createdByUserName");
|
||||
final String comments = rs.getString("comments");
|
||||
final Boolean reversed = rs.getBoolean("reversed");
|
||||
@@ -212,16 +216,20 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
|
||||
TransactionTypeEnumData transactionTypeEnumData = null;
|
||||
|
||||
if (PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) {
|
||||
final LoanTransactionEnumData loanTransactionType = LoanEnumerations
|
||||
.transactionType(JdbcSupport.getInteger(rs, "loanTransactionType"));
|
||||
transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.getId(), loanTransactionType.getCode(),
|
||||
loanTransactionType.getValue());
|
||||
} else if (PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) {
|
||||
final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations
|
||||
.transactionType(JdbcSupport.getInteger(rs, "savingsTransactionType"));
|
||||
transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(),
|
||||
savingsTransactionType.getValue());
|
||||
if (entityTypeId != null && PortfolioAccountType.fromInt(entityTypeId).isLoanAccount()) {
|
||||
final Integer loanTxnTypeId = JdbcSupport.getInteger(rs, "loanTransactionType");
|
||||
if (loanTxnTypeId != null) {
|
||||
final LoanTransactionEnumData loanTransactionType = LoanEnumerations.transactionType(loanTxnTypeId);
|
||||
transactionTypeEnumData = new TransactionTypeEnumData(loanTransactionType.getId(), loanTransactionType.getCode(),
|
||||
loanTransactionType.getValue());
|
||||
}
|
||||
} else if (entityTypeId != null && PortfolioAccountType.fromInt(entityTypeId).isSavingsAccount()) {
|
||||
final Integer savingsTxnTypeId = JdbcSupport.getInteger(rs, "savingsTransactionType");
|
||||
if (savingsTxnTypeId != null) {
|
||||
final SavingsAccountTransactionEnumData savingsTransactionType = SavingsEnumerations.transactionType(savingsTxnTypeId);
|
||||
transactionTypeEnumData = new TransactionTypeEnumData(savingsTransactionType.getId(), savingsTransactionType.getCode(),
|
||||
savingsTransactionType.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData);
|
||||
@@ -538,4 +546,212 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
|
||||
throw new JournalEntriesNotFoundException(entityId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public GLAccountLedgerReportData retrieveGLAccountLedger(final Long glAccountId, final Long officeId,
|
||||
final Boolean onlyManualEntries, final LocalDate fromDate, final LocalDate toDate,
|
||||
final String transactionId, final SearchParameters searchParameters) {
|
||||
|
||||
// ── 1. Fetch GL Account details ──────────────────────────────────────
|
||||
GLAccountData glAccountData = null;
|
||||
boolean isDebitNormal = true; // ASSET / EXPENSE = debit-normal
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
glAccountData = this.glAccountReadPlatformService.retrieveGLAccountById(glAccountId,
|
||||
new JournalEntryAssociationParametersData());
|
||||
if (glAccountData != null && glAccountData.getType() != null) {
|
||||
final int typeId = glAccountData.getType().getId().intValue();
|
||||
// 1=ASSET, 2=LIABILITY, 3=EQUITY, 4=INCOME, 5=EXPENSE
|
||||
isDebitNormal = (typeId == 1 || typeId == 5);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 2. Compute opening balance (all entries before fromDate) ─────────
|
||||
BigDecimal openingBalance = BigDecimal.ZERO;
|
||||
if (glAccountId != null && glAccountId > 0 && fromDate != null) {
|
||||
final List<Object> obParams = new ArrayList<>();
|
||||
final StringBuilder obSql = new StringBuilder(
|
||||
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS ob_debit, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS ob_credit "
|
||||
+ "FROM acc_gl_journal_entry je "
|
||||
+ "WHERE je.account_id = ? AND je.entry_date < ?");
|
||||
obParams.add(glAccountId);
|
||||
obParams.add(fromDate);
|
||||
if (officeId != null && officeId > 0) {
|
||||
obSql.append(" AND je.office_id = ?");
|
||||
obParams.add(officeId);
|
||||
}
|
||||
final BigDecimal[] ob = this.jdbcTemplate.queryForObject(obSql.toString(),
|
||||
(rs, rowNum) -> new BigDecimal[]{
|
||||
Objects.requireNonNullElse(rs.getBigDecimal("ob_debit"), BigDecimal.ZERO),
|
||||
Objects.requireNonNullElse(rs.getBigDecimal("ob_credit"), BigDecimal.ZERO)
|
||||
}, obParams.toArray());
|
||||
if (ob != null) {
|
||||
openingBalance = isDebitNormal ? ob[0].subtract(ob[1]) : ob[1].subtract(ob[0]);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 3. Day-wise summary for the period ───────────────────────────────
|
||||
final List<DayWiseSummaryData> dayWiseSummary = new ArrayList<>();
|
||||
{
|
||||
final List<Object> dwParams = new ArrayList<>();
|
||||
final StringBuilder dwSql = new StringBuilder(
|
||||
"SELECT je.entry_date AS dw_date, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS dw_debit, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS dw_credit, "
|
||||
+ "COUNT(*) AS dw_count "
|
||||
+ "FROM acc_gl_journal_entry je "
|
||||
+ "WHERE 1=1");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
dwSql.append(" AND je.account_id = ?");
|
||||
dwParams.add(glAccountId);
|
||||
}
|
||||
if (officeId != null && officeId > 0) {
|
||||
dwSql.append(" AND je.office_id = ?");
|
||||
dwParams.add(officeId);
|
||||
}
|
||||
if (fromDate != null) {
|
||||
dwSql.append(" AND je.entry_date >= ?");
|
||||
dwParams.add(fromDate);
|
||||
}
|
||||
if (toDate != null) {
|
||||
dwSql.append(" AND je.entry_date <= ?");
|
||||
dwParams.add(toDate);
|
||||
}
|
||||
if (onlyManualEntries != null && onlyManualEntries) {
|
||||
dwSql.append(" AND je.manual_entry = true");
|
||||
}
|
||||
dwSql.append(" GROUP BY je.entry_date ORDER BY je.entry_date ASC");
|
||||
|
||||
final boolean debitNormalFinal = isDebitNormal;
|
||||
final List<Map<String, Object>> rows = this.jdbcTemplate.queryForList(dwSql.toString(), dwParams.toArray());
|
||||
|
||||
BigDecimal running = openingBalance;
|
||||
for (final Map<String, Object> row : rows) {
|
||||
// Handle both java.sql.Date (older drivers) and java.time.LocalDate (newer drivers)
|
||||
final Object rawDate = row.get("dw_date");
|
||||
LocalDate date;
|
||||
if (rawDate instanceof LocalDate) {
|
||||
date = (LocalDate) rawDate;
|
||||
} else if (rawDate instanceof java.sql.Date) {
|
||||
date = ((java.sql.Date) rawDate).toLocalDate();
|
||||
} else {
|
||||
date = LocalDate.parse(String.valueOf(rawDate).substring(0, 10));
|
||||
}
|
||||
final BigDecimal dr = Objects.requireNonNullElse((BigDecimal) row.get("dw_debit"), BigDecimal.ZERO);
|
||||
final BigDecimal cr = Objects.requireNonNullElse((BigDecimal) row.get("dw_credit"), BigDecimal.ZERO);
|
||||
final int count = ((Number) row.get("dw_count")).intValue();
|
||||
final BigDecimal net = debitNormalFinal ? dr.subtract(cr) : cr.subtract(dr);
|
||||
final BigDecimal closing = running.add(net);
|
||||
dayWiseSummary.add(DayWiseSummaryData.builder()
|
||||
.date(date)
|
||||
.openingBalance(running)
|
||||
.totalDebit(dr)
|
||||
.totalCredit(cr)
|
||||
.netMovement(net)
|
||||
.closingBalance(closing)
|
||||
.entryCount(count)
|
||||
.build());
|
||||
running = closing;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 4. Paginated journal entries — only fetch if limit > 0 ───────────
|
||||
final JournalEntryAssociationParametersData assocParams = new JournalEntryAssociationParametersData(true, true);
|
||||
final int reqLimit = searchParameters != null && searchParameters.getLimit() != null
|
||||
? searchParameters.getLimit() : 0;
|
||||
final Page<JournalEntryData> entries;
|
||||
if (reqLimit > 0) {
|
||||
entries = retrieveAll(searchParameters, glAccountId, onlyManualEntries,
|
||||
fromDate, toDate, null, null, transactionId, null, assocParams);
|
||||
} else {
|
||||
// Summary-only request — return empty page, no SQL needed
|
||||
entries = new Page<>(new ArrayList<>(), 0);
|
||||
}
|
||||
|
||||
// ── 5. Compute period totals ─────────────────────────────────────────
|
||||
BigDecimal totalDebit = BigDecimal.ZERO;
|
||||
BigDecimal totalCredit = BigDecimal.ZERO;
|
||||
{
|
||||
final List<Object> totParams = new ArrayList<>();
|
||||
final StringBuilder totSql = new StringBuilder(
|
||||
"SELECT COALESCE(SUM(CASE WHEN je.type_enum = 2 THEN je.amount ELSE 0 END), 0) AS totalDebit, "
|
||||
+ "COALESCE(SUM(CASE WHEN je.type_enum = 1 THEN je.amount ELSE 0 END), 0) AS totalCredit "
|
||||
+ "FROM acc_gl_journal_entry je WHERE 1=1");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
totSql.append(" AND je.account_id = ?");
|
||||
totParams.add(glAccountId);
|
||||
}
|
||||
if (officeId != null && officeId > 0) {
|
||||
totSql.append(" AND je.office_id = ?");
|
||||
totParams.add(officeId);
|
||||
}
|
||||
if (fromDate != null) {
|
||||
totSql.append(" AND je.entry_date >= ?");
|
||||
totParams.add(fromDate);
|
||||
}
|
||||
if (toDate != null) {
|
||||
totSql.append(" AND je.entry_date <= ?");
|
||||
totParams.add(toDate);
|
||||
}
|
||||
if (onlyManualEntries != null && onlyManualEntries) {
|
||||
totSql.append(" AND je.manual_entry = true");
|
||||
}
|
||||
final BigDecimal[] totResult = this.jdbcTemplate.queryForObject(totSql.toString(),
|
||||
(rs, rowNum) -> new BigDecimal[]{
|
||||
Objects.requireNonNullElse(rs.getBigDecimal("totalDebit"), BigDecimal.ZERO),
|
||||
Objects.requireNonNullElse(rs.getBigDecimal("totalCredit"), BigDecimal.ZERO)
|
||||
}, totParams.toArray());
|
||||
if (totResult != null) {
|
||||
totalDebit = totResult[0];
|
||||
totalCredit = totResult[1];
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal netMovement = isDebitNormal ? totalDebit.subtract(totalCredit) : totalCredit.subtract(totalDebit);
|
||||
final BigDecimal closingBalance = openingBalance.add(netMovement);
|
||||
|
||||
// ── 6. Total entries count ────────────────────────────────────────────
|
||||
long totalEntries = 0L;
|
||||
{
|
||||
final List<Object> cntParams = new ArrayList<>();
|
||||
final StringBuilder cntSql = new StringBuilder(
|
||||
"SELECT COUNT(*) FROM acc_gl_journal_entry je WHERE 1=1");
|
||||
if (glAccountId != null && glAccountId > 0) {
|
||||
cntSql.append(" AND je.account_id = ?");
|
||||
cntParams.add(glAccountId);
|
||||
}
|
||||
if (officeId != null && officeId > 0) {
|
||||
cntSql.append(" AND je.office_id = ?");
|
||||
cntParams.add(officeId);
|
||||
}
|
||||
if (fromDate != null) {
|
||||
cntSql.append(" AND je.entry_date >= ?");
|
||||
cntParams.add(fromDate);
|
||||
}
|
||||
if (toDate != null) {
|
||||
cntSql.append(" AND je.entry_date <= ?");
|
||||
cntParams.add(toDate);
|
||||
}
|
||||
if (onlyManualEntries != null && onlyManualEntries) {
|
||||
cntSql.append(" AND je.manual_entry = true");
|
||||
}
|
||||
final Long cnt = this.jdbcTemplate.queryForObject(cntSql.toString(), Long.class, cntParams.toArray());
|
||||
if (cnt != null) { totalEntries = cnt; }
|
||||
}
|
||||
|
||||
return GLAccountLedgerReportData.builder()
|
||||
.glAccount(glAccountData)
|
||||
.openingBalance(openingBalance)
|
||||
.closingBalance(closingBalance)
|
||||
.totalDebit(totalDebit)
|
||||
.totalCredit(totalCredit)
|
||||
.netMovement(netMovement)
|
||||
.debitNormal(isDebitNormal)
|
||||
.fromDate(fromDate)
|
||||
.toDate(toDate)
|
||||
.dayWiseSummary(dayWiseSummary)
|
||||
.totalEntries(totalEntries)
|
||||
.entries(entries)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -189,7 +189,16 @@ public class AccountAssociationsReadPlatformServiceImpl implements AccountAssoci
|
||||
|
||||
@Override
|
||||
public PortfolioAccountData retriveSavingsAccount(final Long savingsId) {
|
||||
String accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId);
|
||||
String accountNo = null;
|
||||
try {
|
||||
accountNo = jdbcTemplate.queryForObject("select account_no from m_savings_account where id = ?", String.class, savingsId);
|
||||
} catch (EmptyResultDataAccessException e) {
|
||||
try {
|
||||
accountNo = jdbcTemplate.queryForObject("select account_no from m_current_account where id = ?", String.class, savingsId);
|
||||
} catch (EmptyResultDataAccessException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return PortfolioAccountData.lookup(savingsId, accountNo);
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.service;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
|
||||
@@ -28,4 +29,13 @@ public interface ClientNomineeWritePlatformService {
|
||||
CommandProcessingResult updateNominee(Long clientId, Long nomineeId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult deleteNominee(Long clientId, Long nomineeId);
|
||||
|
||||
/**
|
||||
* Saves nominee from a JSON element already extracted from the account creation payload.
|
||||
* Called internally during account creation (SB/CB/FD/RD) so nominee is persisted
|
||||
* in the same transaction without a separate API call.
|
||||
*/
|
||||
void saveNomineeIfPresent(Long clientId, String accountNo, String accountType, JsonElement nomineeJson);
|
||||
|
||||
void saveNomineesIfPresent(Long clientId, String accountNo, String accountType, JsonElement nomineesJson);
|
||||
}
|
||||
|
||||
+100
-2
@@ -18,10 +18,13 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.client.service;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
@@ -43,6 +46,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class ClientNomineeWritePlatformServiceImpl implements ClientNomineeWritePlatformService {
|
||||
|
||||
private static final String RELATIONSHIP_CODE = "RELATIONSHIP";
|
||||
@@ -283,8 +287,11 @@ public class ClientNomineeWritePlatformServiceImpl implements ClientNomineeWrite
|
||||
if (clientId == null || nomineeClientId == null || accountType == null || accountType.isBlank()) {
|
||||
return;
|
||||
}
|
||||
final long existing = nomineeRepository.countActiveByClientNomineeAndAccountType(
|
||||
clientId, nomineeClientId, accountType, excludeNomineeId);
|
||||
final long existing = (excludeNomineeId != null)
|
||||
? nomineeRepository.countActiveByClientNomineeAndAccountTypeExcluding(
|
||||
clientId, nomineeClientId, accountType, excludeNomineeId)
|
||||
: nomineeRepository.countActiveByClientNomineeAndAccountType(
|
||||
clientId, nomineeClientId, accountType);
|
||||
if (existing > 0) {
|
||||
throw new PlatformDataIntegrityException("error.msg.nominee.already.linked.for.account.type",
|
||||
"This person is already a nominee for one of the client's "
|
||||
@@ -330,4 +337,95 @@ public class ClientNomineeWritePlatformServiceImpl implements ClientNomineeWrite
|
||||
"percentageShare", total);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNomineeIfPresent(final Long clientId, final String accountNo, final String accountType, final JsonElement nomineeJson) {
|
||||
if (nomineeJson == null || nomineeJson.isJsonNull() || !nomineeJson.isJsonObject()) return;
|
||||
final JsonObject n = nomineeJson.getAsJsonObject();
|
||||
|
||||
if (!n.has("nomineeClientId") || n.get("nomineeClientId").isJsonNull()) return;
|
||||
|
||||
try {
|
||||
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
final Long nomineeClientId = n.get("nomineeClientId").getAsLong();
|
||||
|
||||
if (nomineeClientId.equals(clientId)) return; // self-nomination guard
|
||||
|
||||
// Skip if nominee already exists for this exact account
|
||||
if (nomineeRepository.countActiveByClientNomineeAndAccountNoAndType(clientId, nomineeClientId, accountNo, accountType) > 0) {
|
||||
log.info("Nominee already exists for clientId={}, nomineeClientId={}, accountNo={}, accountType={} — skipping duplicate insert",
|
||||
clientId, nomineeClientId, accountNo, accountType);
|
||||
return;
|
||||
}
|
||||
|
||||
final Client nomineeClient = clientRepository.findOneWithNotFoundDetection(nomineeClientId);
|
||||
|
||||
final BigDecimal share = n.has("percentageShare") && !n.get("percentageShare").isJsonNull()
|
||||
? n.get("percentageShare").getAsBigDecimal() : BigDecimal.ZERO;
|
||||
validatePercentageShareForAccount(accountNo, accountType, null, share);
|
||||
|
||||
final ClientNominee nominee = new ClientNominee();
|
||||
nominee.setClient(client);
|
||||
nominee.setNomineeClient(nomineeClient);
|
||||
nominee.setPercentageShare(share);
|
||||
nominee.setIsActive(true);
|
||||
nominee.setAccountNo(accountNo);
|
||||
nominee.setAccountType(accountType);
|
||||
nominee.setNomineeIdentifier(generateNomineeIdentifier(accountNo, accountType));
|
||||
|
||||
if (n.has("relationshipId") && !n.get("relationshipId").isJsonNull()) {
|
||||
final Long relationshipId = n.get("relationshipId").getAsLong();
|
||||
final CodeValue relationship = codeValueRepository.findOneByCodeNameAndIdWithNotFoundDetection(
|
||||
RELATIONSHIP_CODE, relationshipId);
|
||||
nominee.setRelationship(relationship);
|
||||
}
|
||||
|
||||
final boolean isMinor = n.has("nomineeIsMinor") && !n.get("nomineeIsMinor").isJsonNull()
|
||||
&& n.get("nomineeIsMinor").getAsBoolean();
|
||||
nominee.setNomineeIsMinor(isMinor);
|
||||
|
||||
if (isMinor && n.has("guardianClientId") && !n.get("guardianClientId").isJsonNull()) {
|
||||
final Long guardianClientId = n.get("guardianClientId").getAsLong();
|
||||
final Client guardian = clientRepository.findOneWithNotFoundDetection(guardianClientId);
|
||||
nominee.setGuardianClient(guardian);
|
||||
}
|
||||
|
||||
if (n.has("remarks") && !n.get("remarks").isJsonNull()) {
|
||||
nominee.setRemarks(n.get("remarks").getAsString());
|
||||
}
|
||||
|
||||
nomineeRepository.saveAndFlush(nominee);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to save nominee during account creation for clientId={}, accountNo={}: {}", clientId, accountNo, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveNomineesIfPresent(final Long clientId, final String accountNo, final String accountType, final JsonElement nomineesJson) {
|
||||
if (nomineesJson == null || nomineesJson.isJsonNull()) return;
|
||||
if (nomineesJson.isJsonArray()) {
|
||||
final com.google.gson.JsonArray arr = nomineesJson.getAsJsonArray();
|
||||
BigDecimal totalShare = BigDecimal.ZERO;
|
||||
for (com.google.gson.JsonElement el : arr) {
|
||||
if (!el.isJsonObject()) continue;
|
||||
final com.google.gson.JsonObject n = el.getAsJsonObject();
|
||||
if (!n.has("nomineeClientId") || n.get("nomineeClientId").isJsonNull()) continue;
|
||||
final BigDecimal share = n.has("percentageShare") && !n.get("percentageShare").isJsonNull()
|
||||
? n.get("percentageShare").getAsBigDecimal() : BigDecimal.ZERO;
|
||||
totalShare = totalShare.add(share);
|
||||
}
|
||||
if (totalShare.compareTo(new BigDecimal("100")) > 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.nominee.total.share.exceeds.100",
|
||||
"Total nominee percentage share cannot exceed 100%. Provided total: " + totalShare + "%.",
|
||||
"percentageShare", totalShare);
|
||||
}
|
||||
for (com.google.gson.JsonElement el : arr) {
|
||||
if (el.isJsonObject()) saveNomineeIfPresent(clientId, accountNo, accountType, el);
|
||||
}
|
||||
} else if (nomineesJson.isJsonObject()) {
|
||||
// fallback: single nominee object
|
||||
saveNomineeIfPresent(clientId, accountNo, accountType, nomineesJson);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-16
@@ -102,21 +102,6 @@ public class CurrentProductsApiResource {
|
||||
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private String generateNextProductCode(final String tableName, final String prefix) {
|
||||
try {
|
||||
String maxCode = jdbcTemplate.queryForObject(
|
||||
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
|
||||
String.class, prefix + "%");
|
||||
if (maxCode == null || maxCode.isBlank()) {
|
||||
return prefix + "001";
|
||||
}
|
||||
String numStr = maxCode.substring(prefix.length());
|
||||
int num = Integer.parseInt(numStr);
|
||||
return prefix + String.format("%03d", num + 1);
|
||||
} catch (Exception e) {
|
||||
return prefix + "001";
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -306,7 +291,7 @@ public class CurrentProductsApiResource {
|
||||
interestCalculationTypeOption, interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
|
||||
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
|
||||
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions, accountMappingForPayment);
|
||||
savingsProductToReturn.setProductCode(generateNextProductCode("m_current_product", "CA-IND-"));
|
||||
// product code removed
|
||||
}
|
||||
|
||||
return savingsProductToReturn;
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.api;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class FdInterestRateSaveRequest {
|
||||
|
||||
private String fdType;
|
||||
private String versionLabel;
|
||||
private String remarks;
|
||||
private List<SlabRequest> slabs;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public static class SlabRequest {
|
||||
private Integer srNo;
|
||||
private String description;
|
||||
private Integer periodTypeEnum;
|
||||
private Integer fromPeriod;
|
||||
private Integer toPeriod;
|
||||
private BigDecimal annualInterestRate;
|
||||
private BigDecimal seniorCitizenRate;
|
||||
}
|
||||
}
|
||||
+39
@@ -54,9 +54,13 @@ import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import java.util.Locale;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings;
|
||||
import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer;
|
||||
import org.apache.fineract.infrastructure.codes.data.CodeValueData;
|
||||
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
|
||||
@@ -110,6 +114,8 @@ public class FixedDepositProductsApiResource {
|
||||
private final DropdownReadPlatformService dropdownReadPlatformService;
|
||||
private final PaymentTypeReadService paymentTypeReadPlatformService;
|
||||
private final TaxReadPlatformService taxReadPlatformService;
|
||||
private final CodeValueReadPlatformService codeValueReadPlatformService;
|
||||
private final org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -315,6 +321,16 @@ public class FixedDepositProductsApiResource {
|
||||
// interest rate chart template
|
||||
final InterestRateChartData chartTemplate = this.interestRateChartReadPlatformService.template();
|
||||
|
||||
// CBS custom options
|
||||
final Collection<EnumOptionData> interestCalculationTypeOption = retrieveInterestCalculationTypeOption();
|
||||
final Collection<EnumOptionData> periodFrequencyOptions = retrievePeriodFrequencyOptions();
|
||||
final List<CodeValueData> yesNoOptions = new ArrayList<>(
|
||||
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.YES_NO));
|
||||
final List<CodeValueData> accountTypeOptions = new ArrayList<>(
|
||||
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE));
|
||||
final List<CodeValueData> customerSegmentOptions = new ArrayList<>(
|
||||
this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT));
|
||||
|
||||
FixedDepositProductData fixedDepositProductToReturn = null;
|
||||
if (savingsProduct != null) {
|
||||
fixedDepositProductToReturn = FixedDepositProductData.withTemplate(savingsProduct, currencyOptions,
|
||||
@@ -329,11 +345,34 @@ public class FixedDepositProductsApiResource {
|
||||
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
|
||||
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
|
||||
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
|
||||
// product code removed
|
||||
}
|
||||
|
||||
fixedDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption);
|
||||
fixedDepositProductToReturn.setPeriodFrequencyOptions(periodFrequencyOptions);
|
||||
fixedDepositProductToReturn.setYesNoOptions(yesNoOptions);
|
||||
fixedDepositProductToReturn.setAccountTypeOptions(accountTypeOptions);
|
||||
fixedDepositProductToReturn.setCustomerSegmentOptions(customerSegmentOptions);
|
||||
|
||||
return fixedDepositProductToReturn;
|
||||
}
|
||||
|
||||
private Collection<EnumOptionData> retrievePeriodFrequencyOptions() {
|
||||
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.PERIOD_FREQUENCY).stream()
|
||||
.map(codeValue -> new EnumOptionData(codeValue.getId(),
|
||||
"periodFrequencyType." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'), codeValue.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
private Collection<EnumOptionData> retrieveInterestCalculationTypeOption() {
|
||||
return this.codeValueReadPlatformService.retrieveCodeValuesByCode(SavingsApiConstants.INTEREST_CALCULATION_TYPE_OPTION).stream()
|
||||
.map(codeValue -> new EnumOptionData(codeValue.getId(),
|
||||
"interestCalculationTypeOption." + codeValue.getName().toLowerCase(Locale.ROOT).replace(' ', '_'),
|
||||
codeValue.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
|
||||
@DELETE
|
||||
@Path("{productId}")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
|
||||
+1
@@ -342,6 +342,7 @@ final class FixedDepositProductsApiResourceSwagger {
|
||||
public GetFixedDepositProductsGlAccount interestOnSavingsAccount;
|
||||
public GetFixedDepositProductsGlAccount savingsControlAccount;
|
||||
public GetFixedDepositProductsGlAccount transfersInSuspenseAccount;
|
||||
public GetFixedDepositProductsGlAccount escheatLiabilityAccount;
|
||||
public GetFixedDepositProductsGlAccount interestPayableAccount;
|
||||
}
|
||||
|
||||
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.api;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import com.google.gson.JsonSerializer;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponses;
|
||||
import jakarta.ws.rs.Consumes;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.QueryParam;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
|
||||
import org.apache.fineract.portfolio.savings.DepositsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Path("/v1/fixeddepositproducts/baseinterestrates")
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class FixedDepositProductsBaseInterestRatesApiResource {
|
||||
|
||||
private final PlatformSecurityContext context;
|
||||
private final FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService;
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDate.class,
|
||||
(JsonSerializer<LocalDate>) (src, typeOfSrc, ctx) -> new JsonPrimitive(src.toString()))
|
||||
.create();
|
||||
|
||||
@GET
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Get all active FD base interest rate charts", description = "Returns active CBS FD base interest-rate versions with slabs. Optionally filter by fdType (SINGLE, DOUBLE, SPECIAL).")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public String retrieveAllActiveInterestRates(@QueryParam("fdType") final String fdType) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||
final List<FdInterestRateEffectiveData> data = fdType != null && !fdType.isBlank()
|
||||
? this.fdBaseInterestRateReadPlatformService.retrieveActiveInterestRatesByType(fdType)
|
||||
: this.fdBaseInterestRateReadPlatformService.retrieveAllActiveInterestRates();
|
||||
return gson.toJson(data);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("history")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Get all FD base interest rate charts", description = "Returns the full FD base chart history.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public String retrieveHistory() {
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||
final List<FdInterestRateEffectiveData> data = this.fdBaseInterestRateReadPlatformService.retrieveAllInterestRates();
|
||||
return gson.toJson(data);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
@Operation(summary = "Save new FD base interest rate version", description = "Creates a new FD CBS base chart version for the given fdType and deactivates the previous active version.")
|
||||
@ApiResponses({ @ApiResponse(responseCode = "200", description = "OK") })
|
||||
public String saveInterestRates(@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(DepositsApiConstants.FIXED_DEPOSIT_PRODUCT_RESOURCE_NAME);
|
||||
final FdInterestRateSaveRequest request = gson.fromJson(apiRequestBodyAsJson, FdInterestRateSaveRequest.class);
|
||||
final Long createdById = this.context.authenticatedUser().getId();
|
||||
final FdInterestRateEffectiveData saved = this.fdBaseInterestRateReadPlatformService.saveInterestRates(request, createdById);
|
||||
return gson.toJson(saved);
|
||||
}
|
||||
}
|
||||
+15
@@ -45,6 +45,7 @@ import jakarta.ws.rs.core.UriInfo;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -77,6 +78,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountChargeData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountTransactionData;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountPreMatureCalculationPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.RecurringDepositAccountInterestCalculationService;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsAccountChargeReadPlatformService;
|
||||
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
|
||||
import org.glassfish.jersey.media.multipart.FormDataParam;
|
||||
@@ -99,6 +101,7 @@ public class RecurringDepositAccountsApiResource {
|
||||
private final DepositAccountPreMatureCalculationPlatformService accountPreMatureCalculationPlatformService;
|
||||
private final BulkImportWorkbookService bulkImportWorkbookService;
|
||||
private final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService;
|
||||
private final RecurringDepositAccountInterestCalculationService recurringDepositAccountInterestCalculationService;
|
||||
private final SqlValidator sqlValidator;
|
||||
|
||||
@GET
|
||||
@@ -369,6 +372,18 @@ public class RecurringDepositAccountsApiResource {
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("calculate-rd-interest")
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@Produces({ MediaType.APPLICATION_JSON })
|
||||
public String calculateRecurringDepositInterest(@Context final UriInfo uriInfo, final String apiRequestBodyAsJson) {
|
||||
JsonElement jsonElement = fromJsonHelper.parse(apiRequestBodyAsJson);
|
||||
HashMap<String, Object> result = recurringDepositAccountInterestCalculationService
|
||||
.calculateInterest(new JsonQuery(apiRequestBodyAsJson, jsonElement, fromJsonHelper));
|
||||
|
||||
return toApiJsonSerializer.serializeResult(result);
|
||||
}
|
||||
|
||||
private boolean is(final String commandParam, final String commandValue) {
|
||||
return StringUtils.isNotBlank(commandParam) && commandParam.trim().equalsIgnoreCase(commandValue);
|
||||
}
|
||||
|
||||
+1
-16
@@ -333,7 +333,7 @@ public class RecurringDepositProductsApiResource {
|
||||
interestCalculationDaysInYearTypeOptions, lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions,
|
||||
paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions, penaltyOptions, chartTemplate,
|
||||
preClosurePenalInterestOnTypeOptions, periodFrequencyTypeOptions, taxGroupOptions);
|
||||
recurringDepositProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "RD-IND-"));
|
||||
// product code removed
|
||||
}
|
||||
|
||||
recurringDepositProductToReturn.setInterestCalculationTypeOption(interestCalculationTypeOption);
|
||||
@@ -360,21 +360,6 @@ public class RecurringDepositProductsApiResource {
|
||||
.toList();
|
||||
}
|
||||
|
||||
private String generateNextProductCode(final String tableName, final String prefix) {
|
||||
try {
|
||||
String maxCode = jdbcTemplate.queryForObject(
|
||||
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ? AND deposit_type_enum = 300",
|
||||
String.class, prefix + "%");
|
||||
if (maxCode == null || maxCode.isBlank()) {
|
||||
return prefix + "001";
|
||||
}
|
||||
String numStr = maxCode.substring(prefix.length());
|
||||
int num = Integer.parseInt(numStr);
|
||||
return prefix + String.format("%03d", num + 1);
|
||||
} catch (Exception e) {
|
||||
return prefix + "001";
|
||||
}
|
||||
}
|
||||
|
||||
// ── RD Interest Rate Chart endpoints ────────────────────────────────────
|
||||
|
||||
|
||||
+1
@@ -360,6 +360,7 @@ final class RecurringDepositProductsApiResourceSwagger {
|
||||
public GetRecurringDepositProductsGlAccount interestOnSavingsAccount;
|
||||
public GetRecurringDepositProductsGlAccount savingsControlAccount;
|
||||
public GetRecurringDepositProductsGlAccount transfersInSuspenseAccount;
|
||||
public GetRecurringDepositProductsGlAccount escheatLiabilityAccount;
|
||||
public GetRecurringDepositProductsGlAccount feeReceivableAccount;
|
||||
public GetRecurringDepositProductsGlAccount penaltyReceivableAccount;
|
||||
public GetRecurringDepositProductsGlAccount interestPayableAccount;
|
||||
|
||||
+1
-16
@@ -106,21 +106,6 @@ public class SavingsProductsApiResource {
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final JdbcTemplate jdbcTemplate;
|
||||
|
||||
private String generateNextProductCode(final String tableName, final String prefix) {
|
||||
try {
|
||||
String maxCode = jdbcTemplate.queryForObject(
|
||||
"SELECT MAX(product_code) FROM " + tableName + " WHERE product_code LIKE ?",
|
||||
String.class, prefix + "%");
|
||||
if (maxCode == null || maxCode.isBlank()) {
|
||||
return prefix + "001";
|
||||
}
|
||||
String numStr = maxCode.substring(prefix.length());
|
||||
int num = Integer.parseInt(numStr);
|
||||
return prefix + String.format("%03d", num + 1);
|
||||
} catch (Exception e) {
|
||||
return prefix + "001";
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Consumes({ MediaType.APPLICATION_JSON })
|
||||
@@ -315,7 +300,7 @@ public class SavingsProductsApiResource {
|
||||
periodFrequencyOptions, paymentTypeOptions, accountingRuleOptions, accountingMappingOptions, chargeOptions,
|
||||
penaltyOptions, taxGroupOptions, yesNoOptions, accountTypeOptions, customerSegmentOptions,
|
||||
accountMappingForPayment);
|
||||
savingsProductToReturn.setProductCode(generateNextProductCode("m_savings_product", "SB-IND-"));
|
||||
// product code generation removed
|
||||
}
|
||||
|
||||
return savingsProductToReturn;
|
||||
|
||||
+1
@@ -285,6 +285,7 @@ final class SavingsProductsApiResourceSwagger {
|
||||
public GetSavingsProductsGlAccount writeOffAccount;
|
||||
public GetSavingsProductsGlAccount savingsControlAccount;
|
||||
public GetSavingsProductsGlAccount transfersInSuspenseAccount;
|
||||
public GetSavingsProductsGlAccount escheatLiabilityAccount;
|
||||
public GetSavingsProductsGlAccount interestPayableAccount;
|
||||
}
|
||||
|
||||
|
||||
+7
-3
@@ -47,6 +47,7 @@ import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureI
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferInterestToSavingsParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.transferToSavingsIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.accountNoParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.customerSegmentParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.amountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargeIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.chargesParamName;
|
||||
@@ -260,6 +261,9 @@ public class DepositAccountDataValidator {
|
||||
baseDataValidator.reset().parameter(externalIdParamName).value(externalId).notExceedingLengthOf(100);
|
||||
}
|
||||
|
||||
final String customerSegment = this.fromApiJsonHelper.extractStringNamed(customerSegmentParamName, element);
|
||||
baseDataValidator.reset().parameter(customerSegmentParamName).value(customerSegment).notBlank();
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(nominalAnnualInterestRateParamName, element)) {
|
||||
final BigDecimal interestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(nominalAnnualInterestRateParamName,
|
||||
element);
|
||||
@@ -567,7 +571,7 @@ public class DepositAccountDataValidator {
|
||||
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
|
||||
}
|
||||
|
||||
if (depositType.isFixedDeposit() || fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
|
||||
if (depositType.isFixedDeposit() || depositType.isRecurringDeposit() || fromApiJsonHelper.parameterExists(depositPeriodParamName, element)) {
|
||||
final Integer depositPeriod = fromApiJsonHelper.extractIntegerSansLocaleNamed(depositPeriodParamName, element);
|
||||
baseDataValidator.reset().parameter(depositPeriodParamName).value(depositPeriod).notNull().integerGreaterThanZero();
|
||||
}
|
||||
@@ -642,8 +646,8 @@ public class DepositAccountDataValidator {
|
||||
|
||||
final BigDecimal mandatoryRecommendedDepositAmount = fromApiJsonHelper
|
||||
.extractBigDecimalWithLocaleNamed(mandatoryRecommendedDepositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(mandatoryRecommendedDepositAmountParamName).value(mandatoryRecommendedDepositAmount).notNull()
|
||||
.positiveAmount();
|
||||
baseDataValidator.reset().parameter(mandatoryRecommendedDepositAmountParamName).value(mandatoryRecommendedDepositAmount)
|
||||
.notNull().positiveAmount();
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(isMandatoryDepositParamName, element)) {
|
||||
final Boolean isMandatoryDeposit = this.fromApiJsonHelper.extractBooleanNamed(isMandatoryDepositParamName, element);
|
||||
|
||||
+7
-3
@@ -632,8 +632,12 @@ public class DepositProductDataValidator {
|
||||
}
|
||||
|
||||
private void validateDepositAmountForCreate(JsonElement element, DataValidatorBuilder baseDataValidator) {
|
||||
final BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
|
||||
final BigDecimal depositAmount = this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)
|
||||
? this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element)
|
||||
: null;
|
||||
if (depositAmount != null) {
|
||||
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount();
|
||||
}
|
||||
|
||||
BigDecimal depositMinAmount = null;
|
||||
if (this.fromApiJsonHelper.parameterExists(depositMinAmountParamName, element)) {
|
||||
@@ -667,7 +671,7 @@ public class DepositProductDataValidator {
|
||||
|
||||
if (this.fromApiJsonHelper.parameterExists(depositAmountParamName, element)) {
|
||||
depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
|
||||
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).notNull().positiveAmount();
|
||||
baseDataValidator.reset().parameter(depositAmountParamName).value(depositAmount).positiveAmount();
|
||||
}
|
||||
|
||||
BigDecimal depositMinAmount = null;
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class FdInterestRateEffectiveData {
|
||||
|
||||
private Long id;
|
||||
private String fdType;
|
||||
private String versionLabel;
|
||||
private LocalDate effectiveFromDate;
|
||||
private LocalDate effectiveToDate;
|
||||
private Boolean isActive;
|
||||
private String remarks;
|
||||
private List<FdInterestRateSlabData> slabs;
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@Builder
|
||||
public class FdInterestRateSlabData {
|
||||
|
||||
private Long id;
|
||||
private Integer srNo;
|
||||
private String description;
|
||||
private Integer periodTypeEnum;
|
||||
private String periodTypeLabel;
|
||||
private Integer fromPeriod;
|
||||
private Integer toPeriod;
|
||||
private BigDecimal annualInterestRate;
|
||||
private BigDecimal seniorCitizenRate;
|
||||
}
|
||||
+7
-1
@@ -120,6 +120,7 @@ public class DepositAccountAssembler {
|
||||
private final PaymentDetailAssembler paymentDetailAssembler;
|
||||
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
|
||||
@Autowired
|
||||
public DepositAccountAssembler(final SavingsAccountTransactionSummaryWrapper savingsAccountTransactionSummaryWrapper,
|
||||
@@ -130,7 +131,8 @@ public class DepositAccountAssembler {
|
||||
final DepositProductAssembler depositProductAssembler,
|
||||
final RecurringDepositProductRepository recurringDepositProductRepository,
|
||||
final AccountTransfersReadPlatformService accountTransfersReadPlatformService, final PlatformSecurityContext context,
|
||||
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory) {
|
||||
final PaymentDetailAssembler paymentDetailAssembler, ExternalIdFactory externalIdFactory,
|
||||
final SavingsAccountAssembler savingsAccountAssembler) {
|
||||
|
||||
this.savingsAccountTransactionSummaryWrapper = savingsAccountTransactionSummaryWrapper;
|
||||
this.clientRepository = clientRepository;
|
||||
@@ -146,6 +148,7 @@ public class DepositAccountAssembler {
|
||||
this.context = context;
|
||||
this.paymentDetailAssembler = paymentDetailAssembler;
|
||||
this.externalIdFactory = externalIdFactory;
|
||||
this.savingsAccountAssembler = savingsAccountAssembler;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -358,6 +361,9 @@ public class DepositAccountAssembler {
|
||||
if (account != null) {
|
||||
account.setHelpers(this.savingsAccountTransactionSummaryWrapper, this.savingsHelper);
|
||||
account.validateNewApplicationState(depositAccountType.resourceName());
|
||||
// Apply CBS-specific fields (accountStatus, customerSegment, autoDebitFacility,
|
||||
// linkedAccount, nominee, jointHolders, guardian, etc.)
|
||||
this.savingsAccountAssembler.applyCbsFields(command, element, account);
|
||||
}
|
||||
|
||||
return account;
|
||||
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_fd_base_interest_rate_effective")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateEffective {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "fd_type", length = 20)
|
||||
private String fdType;
|
||||
|
||||
@Column(name = "version_label")
|
||||
private String versionLabel;
|
||||
|
||||
@Column(name = "effective_from_date")
|
||||
private LocalDate effectiveFromDate;
|
||||
|
||||
@Column(name = "effective_to_date")
|
||||
private LocalDate effectiveToDate;
|
||||
|
||||
@Column(name = "is_active")
|
||||
private Boolean isActive;
|
||||
|
||||
@Column(name = "remarks")
|
||||
private String remarks;
|
||||
|
||||
@Column(name = "created_by_id")
|
||||
private Long createdById;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateEffective create(String fdType, String versionLabel, LocalDate effectiveFromDate, String remarks,
|
||||
Long createdById) {
|
||||
FdBaseInterestRateEffective e = new FdBaseInterestRateEffective();
|
||||
e.fdType = fdType;
|
||||
e.versionLabel = versionLabel;
|
||||
e.effectiveFromDate = effectiveFromDate;
|
||||
e.isActive = true;
|
||||
e.remarks = remarks;
|
||||
e.createdById = createdById;
|
||||
e.createdDate = LocalDate.now();
|
||||
return e;
|
||||
}
|
||||
|
||||
public void deactivate(LocalDate toDate) {
|
||||
this.isActive = false;
|
||||
this.effectiveToDate = toDate;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface FdBaseInterestRateEffectiveRepository extends JpaRepository<FdBaseInterestRateEffective, Long> {
|
||||
|
||||
List<FdBaseInterestRateEffective> findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(String fdType);
|
||||
|
||||
List<FdBaseInterestRateEffective> findByFdTypeOrderByEffectiveFromDateDesc(String fdType);
|
||||
|
||||
List<FdBaseInterestRateEffective> findByIsActiveTrueOrderByEffectiveFromDateDesc();
|
||||
|
||||
List<FdBaseInterestRateEffective> findByOrderByEffectiveFromDateDesc();
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_fd_base_interest_rate_slab_history")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public class FdBaseInterestRateSlabHistory {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "effective_id")
|
||||
private Long effectiveId;
|
||||
|
||||
@Column(name = "sr_no")
|
||||
private Integer srNo;
|
||||
|
||||
@Column(name = "description")
|
||||
private String description;
|
||||
|
||||
@Column(name = "period_type_enum")
|
||||
private Integer periodTypeEnum;
|
||||
|
||||
@Column(name = "from_period")
|
||||
private Integer fromPeriod;
|
||||
|
||||
@Column(name = "to_period")
|
||||
private Integer toPeriod;
|
||||
|
||||
@Column(name = "annual_interest_rate")
|
||||
private BigDecimal annualInterestRate;
|
||||
|
||||
@Column(name = "senior_citizen_rate")
|
||||
private BigDecimal seniorCitizenRate;
|
||||
|
||||
@Column(name = "created_date")
|
||||
private LocalDate createdDate;
|
||||
|
||||
public static FdBaseInterestRateSlabHistory create(Long effectiveId, Integer srNo, String description, Integer periodTypeEnum,
|
||||
Integer fromPeriod, Integer toPeriod, BigDecimal annualInterestRate, BigDecimal seniorCitizenRate) {
|
||||
FdBaseInterestRateSlabHistory s = new FdBaseInterestRateSlabHistory();
|
||||
s.effectiveId = effectiveId;
|
||||
s.srNo = srNo;
|
||||
s.description = description;
|
||||
s.periodTypeEnum = periodTypeEnum != null ? periodTypeEnum : 1;
|
||||
s.fromPeriod = fromPeriod;
|
||||
s.toPeriod = toPeriod;
|
||||
s.annualInterestRate = annualInterestRate;
|
||||
s.seniorCitizenRate = seniorCitizenRate;
|
||||
s.createdDate = LocalDate.now();
|
||||
return s;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.domain;
|
||||
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface FdBaseInterestRateSlabHistoryRepository extends JpaRepository<FdBaseInterestRateSlabHistory, Long> {
|
||||
|
||||
List<FdBaseInterestRateSlabHistory> findByEffectiveIdOrderBySrNo(Long effectiveId);
|
||||
}
|
||||
+8
-8
@@ -230,12 +230,14 @@ public class RecurringDepositAccount extends SavingsAccount {
|
||||
}
|
||||
|
||||
final BigDecimal depositAmount = accountTermAndPreClosure.depositAmount();
|
||||
BigDecimal applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate,
|
||||
this.client);
|
||||
BigDecimal applicableInterestRate = BigDecimal.ZERO;
|
||||
if (this.chart != null) {
|
||||
applicableInterestRate = this.chart.getApplicableInterestRate(depositAmount, depositStartDate(), depositCloseDate, this.client);
|
||||
|
||||
if (applyPreMaturePenalty) {
|
||||
applicableInterestRate = applicableInterestRate.subtract(penalInterest);
|
||||
applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate;
|
||||
if (applyPreMaturePenalty) {
|
||||
applicableInterestRate = applicableInterestRate.subtract(penalInterest);
|
||||
applicableInterestRate = applicableInterestRate.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : applicableInterestRate;
|
||||
}
|
||||
}
|
||||
|
||||
this.nominalAnnualInterestRate = applicableInterestRate;
|
||||
@@ -1070,10 +1072,8 @@ public class RecurringDepositAccount extends SavingsAccount {
|
||||
.failWithCodeNoParameterAddedToErrorCode("no.applicable.interest.rate.is.found.based.on.amount.and.deposit.period");
|
||||
}
|
||||
|
||||
} else if (this.nominalAnnualInterestRate == null || this.nominalAnnualInterestRate.compareTo(BigDecimal.ZERO) == 0) {
|
||||
baseDataValidator.reset().parameter(DepositsApiConstants.nominalAnnualInterestRateParamName).value(nominalAnnualInterestRate)
|
||||
.failWithCodeNoParameterAddedToErrorCode("interest.chart.or.nominal.interest.rate.required");
|
||||
}
|
||||
// CBS: chart is optional — CBS base interest rate chart is used instead
|
||||
if (!dataValidationErrors.isEmpty()) {
|
||||
throw new PlatformApiDataValidationException(dataValidationErrors);
|
||||
}
|
||||
|
||||
+13
-2
@@ -40,6 +40,8 @@ import static org.apache.fineract.portfolio.savings.SavingsApiConstants.minorDat
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nomineeParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.specialCategoryParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.weakerSectionParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.nominationFacilityParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.autoDebitFacilityParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.allowOverdraftParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.clientIdParamName;
|
||||
import static org.apache.fineract.portfolio.savings.SavingsApiConstants.enforceMinRequiredBalanceParamName;
|
||||
@@ -367,7 +369,7 @@ public class SavingsAccountAssembler {
|
||||
|
||||
private static final Gson GSON = new Gson();
|
||||
|
||||
private void applyCbsFields(final JsonCommand command, final JsonElement element, final SavingsAccount account) {
|
||||
public 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);
|
||||
@@ -384,6 +386,14 @@ public class SavingsAccountAssembler {
|
||||
|
||||
account.setSpecialCategory(this.fromApiJsonHelper.extractStringNamed(specialCategoryParamName, element));
|
||||
account.setWeakerSection(this.fromApiJsonHelper.extractStringNamed(weakerSectionParamName, element));
|
||||
|
||||
if (command.parameterExists(nominationFacilityParamName)) {
|
||||
account.setNominationFacility(this.fromApiJsonHelper.extractBooleanNamed(nominationFacilityParamName, element));
|
||||
}
|
||||
if (command.parameterExists(autoDebitFacilityParamName)) {
|
||||
account.setAutoDebitFacility(this.fromApiJsonHelper.extractBooleanNamed(autoDebitFacilityParamName, element));
|
||||
}
|
||||
|
||||
account.setGuardianName(this.fromApiJsonHelper.extractStringNamed(guardianNameParamName, element));
|
||||
account.setGuardianCifNumber(this.fromApiJsonHelper.extractStringNamed(guardianCifNumberParamName, element));
|
||||
account.setGuardianRelationship(this.fromApiJsonHelper.extractStringNamed(guardianRelationshipParamName, element));
|
||||
@@ -458,8 +468,9 @@ public class SavingsAccountAssembler {
|
||||
role = isPrimary ? "Primary Account Holder" : "Co-Holder";
|
||||
}
|
||||
|
||||
final boolean isMandatory = h.has("isMandatory") && h.get("isMandatory").getAsBoolean();
|
||||
holders.add(new SavingsAccountJointHolder(account, clientId, cifNumber, displayName,
|
||||
sharePercentage, signingAuthority, role, isPrimary));
|
||||
sharePercentage, signingAuthority, role, isPrimary, isMandatory));
|
||||
}
|
||||
account.getJointHolders().clear();
|
||||
account.getJointHolders().addAll(holders);
|
||||
|
||||
+160
-2
@@ -63,7 +63,154 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
|
||||
if (rows.isEmpty()) {
|
||||
throw new CurrentAccountNotFoundException(accountId);
|
||||
}
|
||||
return buildStructuredResponse(rows.get(0));
|
||||
final Map<String, Object> response = buildStructuredResponse(rows.get(0));
|
||||
response.put("charges", retrieveCharges(accountId));
|
||||
response.put("transactions", retrieveTransactions(accountId));
|
||||
return response;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> retrieveTransactions(Long accountId) {
|
||||
final String sql =
|
||||
"SELECT t.id, t.transaction_type_enum, t.transaction_date, t.amount, "
|
||||
+ "t.running_balance_derived, t.is_reversed, t.is_reversal, t.ref_no, t.remarks, "
|
||||
+ "t.submitted_on_date "
|
||||
+ "FROM m_current_account_transaction t "
|
||||
+ "WHERE t.current_account_id = ? "
|
||||
+ "ORDER BY t.transaction_date ASC, t.id ASC";
|
||||
|
||||
return jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
final Map<String, Object> t = new LinkedHashMap<>();
|
||||
t.put("id", rs.getLong("id"));
|
||||
|
||||
final int txnTypeEnum = rs.getInt("transaction_type_enum");
|
||||
t.put("transactionType", buildTransactionType(txnTypeEnum));
|
||||
|
||||
final java.time.LocalDate txnDate = rs.getObject("transaction_date", java.time.LocalDate.class);
|
||||
if (txnDate != null) {
|
||||
t.put("date", new int[]{txnDate.getYear(), txnDate.getMonthValue(), txnDate.getDayOfMonth()});
|
||||
} else {
|
||||
t.put("date", null);
|
||||
}
|
||||
|
||||
t.put("amount", rs.getBigDecimal("amount"));
|
||||
t.put("runningBalance", rs.getBigDecimal("running_balance_derived"));
|
||||
t.put("reversed", rs.getBoolean("is_reversed"));
|
||||
t.put("isReversal", rs.getBoolean("is_reversal"));
|
||||
t.put("refNo", rs.getString("ref_no"));
|
||||
t.put("remarks", rs.getString("remarks"));
|
||||
|
||||
final java.time.LocalDate submittedOn = rs.getObject("submitted_on_date", java.time.LocalDate.class);
|
||||
if (submittedOn != null) {
|
||||
t.put("submittedOnDate", new int[]{submittedOn.getYear(), submittedOn.getMonthValue(), submittedOn.getDayOfMonth()});
|
||||
} else {
|
||||
t.put("submittedOnDate", null);
|
||||
}
|
||||
return t;
|
||||
}, accountId);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildTransactionType(int typeEnum) {
|
||||
final Map<String, Object> tt = new LinkedHashMap<>();
|
||||
tt.put("id", typeEnum);
|
||||
tt.put("deposit", typeEnum == 1);
|
||||
tt.put("withdrawal", typeEnum == 2);
|
||||
tt.put("interestPosting", typeEnum == 3);
|
||||
tt.put("feeDeduction", typeEnum == 4 || typeEnum == 7);
|
||||
tt.put("overdraftInterest", false);
|
||||
tt.put("withholdTax", false);
|
||||
tt.put("initiateTransfer", false);
|
||||
tt.put("approveTransfer", false);
|
||||
tt.put("withdrawTransfer", false);
|
||||
tt.put("rejectTransfer", false);
|
||||
tt.put("writtenoff", false);
|
||||
tt.put("overdraftFee", false);
|
||||
tt.put("escheat", false);
|
||||
tt.put("amountHold", false);
|
||||
tt.put("amountRelease", false);
|
||||
tt.put("accrual", false);
|
||||
switch (typeEnum) {
|
||||
case 1 -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
|
||||
case 2 -> { tt.put("code", "savingsTransactionType.withdrawal"); tt.put("value", "Withdrawal"); }
|
||||
case 3 -> { tt.put("code", "savingsTransactionType.interestPosting"); tt.put("value", "Interest Posting"); }
|
||||
case 4 -> { tt.put("code", "savingsTransactionType.feeDeduction"); tt.put("value", "Fee Deduction"); }
|
||||
case 7 -> { tt.put("code", "savingsTransactionType.payCharge"); tt.put("value", "Charge Deduction"); }
|
||||
default -> { tt.put("code", "savingsTransactionType.deposit"); tt.put("value", "Deposit"); }
|
||||
}
|
||||
return tt;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> retrieveCharges(Long accountId) {
|
||||
final String sql =
|
||||
"SELECT cac.id, cac.charge_id, ch.name AS charge_name, "
|
||||
+ "cac.charge_time_enum, cac.charge_due_date, cac.fee_on_month, cac.fee_on_day, "
|
||||
+ "cac.fee_interval, cac.charge_calculation_enum, "
|
||||
+ "cac.amount, cac.amount_paid_derived, cac.amount_waived_derived, "
|
||||
+ "cac.amount_writtenoff_derived, cac.amount_outstanding_derived, "
|
||||
+ "cac.is_paid_derived, cac.waived, cac.is_penalty, cac.is_active "
|
||||
+ "FROM m_current_account_charge cac "
|
||||
+ "JOIN m_charge ch ON ch.id = cac.charge_id "
|
||||
+ "WHERE cac.current_account_id = ? AND cac.is_active = true "
|
||||
+ "ORDER BY cac.id";
|
||||
|
||||
return jdbcTemplate.query(sql, (rs, rowNum) -> {
|
||||
final Map<String, Object> c = new LinkedHashMap<>();
|
||||
c.put("id", rs.getLong("id"));
|
||||
c.put("chargeId", rs.getLong("charge_id"));
|
||||
c.put("name", rs.getString("charge_name"));
|
||||
|
||||
final int chargeTimeEnum = rs.getInt("charge_time_enum");
|
||||
c.put("chargeTimeType", buildEnumOption(chargeTimeEnum, chargeTimeValue(chargeTimeEnum)));
|
||||
|
||||
c.put("chargeDueDate", rs.getDate("charge_due_date") != null ? rs.getDate("charge_due_date").toLocalDate().toString() : null);
|
||||
c.put("feeOnMonthDay", buildFeeOnMonthDay(rs.getObject("fee_on_month"), rs.getObject("fee_on_day")));
|
||||
c.put("feeInterval", rs.getObject("fee_interval"));
|
||||
|
||||
final int chargeCalcEnum = rs.getInt("charge_calculation_enum");
|
||||
c.put("chargeCalculationType", buildEnumOption(chargeCalcEnum, chargeCalcValue(chargeCalcEnum)));
|
||||
|
||||
c.put("amount", rs.getBigDecimal("amount"));
|
||||
c.put("amountPaid", rs.getBigDecimal("amount_paid_derived"));
|
||||
c.put("amountWaived", rs.getBigDecimal("amount_waived_derived"));
|
||||
c.put("amountWrittenOff", rs.getBigDecimal("amount_writtenoff_derived"));
|
||||
c.put("amountOutstanding", rs.getBigDecimal("amount_outstanding_derived"));
|
||||
c.put("paid", rs.getBoolean("is_paid_derived"));
|
||||
c.put("waived", rs.getBoolean("waived"));
|
||||
c.put("penalty", rs.getBoolean("is_penalty"));
|
||||
c.put("isActive", rs.getBoolean("is_active"));
|
||||
return c;
|
||||
}, accountId);
|
||||
}
|
||||
|
||||
private Map<String, Object> buildFeeOnMonthDay(Object month, Object day) {
|
||||
if (month == null || day == null) return null;
|
||||
final Map<String, Object> m = new LinkedHashMap<>();
|
||||
m.put("month", month);
|
||||
m.put("day", day);
|
||||
return m;
|
||||
}
|
||||
|
||||
private String chargeTimeValue(int id) {
|
||||
return switch (id) {
|
||||
case 1 -> "Disbursement";
|
||||
case 2 -> "Specified due date";
|
||||
case 3 -> "Savings Activation";
|
||||
case 4 -> "Withdrawal Fee";
|
||||
case 5 -> "Annual Fee";
|
||||
case 6 -> "Annual Fee";
|
||||
case 7 -> "Monthly Fee";
|
||||
case 8 -> "Overdue Fee";
|
||||
default -> "Specified due date";
|
||||
};
|
||||
}
|
||||
|
||||
private String chargeCalcValue(int id) {
|
||||
return switch (id) {
|
||||
case 1 -> "Flat";
|
||||
case 2 -> "% Amount";
|
||||
case 3 -> "% Loan Amount + Interest";
|
||||
case 4 -> "% Interest";
|
||||
default -> "Flat";
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -71,7 +218,10 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
|
||||
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));
|
||||
final Map<String, Object> response = buildStructuredResponse(row);
|
||||
final Long id = ((Number) row.get("id")).longValue();
|
||||
response.put("charges", retrieveCharges(id));
|
||||
result.add(response);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -88,10 +238,12 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
|
||||
out.put("groupId", row.get("group_id"));
|
||||
out.put("productId", row.get("product_id"));
|
||||
out.put("productName", row.get("product_name"));
|
||||
out.put("currentAccountProductName", 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("fieldOfficerId", row.get("staff_id"));
|
||||
out.put("fieldOfficerName", row.get("staff_name"));
|
||||
|
||||
// status
|
||||
@@ -234,6 +386,12 @@ public class CurrentAccountReadPlatformServiceImpl implements CurrentAccountRead
|
||||
};
|
||||
s.put("code", "SavingsAccountSubStatusEnum." + value.replace(" ", ""));
|
||||
s.put("value", value);
|
||||
s.put("none", subStatusEnum == 0);
|
||||
s.put("inactive", subStatusEnum == 100);
|
||||
s.put("dormant", subStatusEnum == 200);
|
||||
s.put("block", subStatusEnum == 300);
|
||||
s.put("blockDebit", subStatusEnum == 400);
|
||||
s.put("blockCredit", subStatusEnum == 500);
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+325
-3
@@ -29,11 +29,13 @@ 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.core.exception.PlatformDataIntegrityException;
|
||||
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.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.service.ClientNomineeWritePlatformService;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccount;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig;
|
||||
import org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountJointHolder;
|
||||
@@ -43,6 +45,14 @@ import org.apache.fineract.portfolio.currentaccount.service.CurrentAccountWriteP
|
||||
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.accounting.common.AccountingConstants.CashAccountsForSavings;
|
||||
import org.apache.fineract.accounting.common.AccountingRuleType;
|
||||
import org.apache.fineract.accounting.glaccount.domain.GLAccount;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntry;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryRepository;
|
||||
import org.apache.fineract.accounting.journalentry.domain.JournalEntryType;
|
||||
import org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
|
||||
import org.apache.fineract.portfolio.PortfolioProductType;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsAccountNotFoundException;
|
||||
import org.apache.fineract.portfolio.savings.exception.SavingsProductNotFoundException;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -59,6 +69,11 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
private final ClientRepositoryWrapper clientRepository;
|
||||
private final OfficeRepositoryWrapper officeRepository;
|
||||
private final CurrentAccountNumberGenerator accountNumberGenerator;
|
||||
private final ClientNomineeWritePlatformService clientNomineeWritePlatformService;
|
||||
private final org.apache.fineract.portfolio.savings.domain.FacilityChargeMappingRepository facilityChargeMappingRepository;
|
||||
private final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransactionRepository currentAccountTransactionRepository;
|
||||
private final JournalEntryRepository journalEntryRepository;
|
||||
private final ProductToGLAccountMappingRepository productToGLAccountMappingRepository;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@@ -74,6 +89,13 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
final Client client = clientRepository.findOneWithNotFoundDetection(clientId);
|
||||
final CurrentProduct product = currentProductRepository.findById(productId)
|
||||
.orElseThrow(() -> new SavingsProductNotFoundException(productId));
|
||||
|
||||
if (currentAccountRepository.countActiveByClientAndProduct(clientId, productId) > 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.savings.account.duplicate.product.for.client",
|
||||
"Client already has an active Current Account with the same product.",
|
||||
"productId", productId);
|
||||
}
|
||||
final Office office = officeId != null
|
||||
? officeRepository.findOneWithNotFoundDetection(officeId)
|
||||
: client.getOffice();
|
||||
@@ -112,6 +134,26 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
product.getInterestCalculateSelector(), product.getAveragePeriod(),
|
||||
product.getAvgBalanceCalcPeriod());
|
||||
|
||||
// Copy product-level fields that are not in createNew()
|
||||
account.setMinRequiredOpeningBalance(product.getMinRequiredOpeningBalance());
|
||||
account.setMinRequiredBalance(product.getMinRequiredBalance());
|
||||
account.setEnforceMinRequiredBalance(product.isEnforceMinRequiredBalance());
|
||||
account.setLockinPeriodFrequency(product.getLockinPeriodFrequency());
|
||||
account.setLockinPeriodFrequencyEnum(product.getLockinPeriodFrequencyType());
|
||||
account.setNominalAnnualInterestRateOverdraft(product.getNominalAnnualInterestRateOverdraft());
|
||||
account.setWithdrawalFeeForTransfer(product.isWithdrawalFeeApplicableForTransfer());
|
||||
account.setMinOverdraftForInterestCalculation(product.getMinOverdraftForInterestCalculation());
|
||||
account.setFrequencyFixDay(product.getFrequencyFixDay());
|
||||
account.setEnableLockinPeriod(product.isEnableLockinPeriod());
|
||||
account.setBalanceFrequency(product.getBalanceFrequency());
|
||||
account.setAccountClosedZeroBalanceInDayClosing(product.getAccountClosedZeroBalanceInDayClosing());
|
||||
account.setValidForPeriod(product.getValidForPeriod());
|
||||
account.setOdAccountClosedZeroBalanceInDayClosing(product.isOdAccountClosedZeroBalanceInDayClosing());
|
||||
account.setLienAllowed(product.isLienAllowed());
|
||||
account.setMaxAllowedLienLimit(product.getMaxAllowedLienLimit());
|
||||
account.setMinBalanceForInterestCalculation(product.getMinBalanceForInterestCalculation());
|
||||
account.setWithholdTax(product.withHoldTax());
|
||||
|
||||
if (json.has("establishmentDate") && !json.get("establishmentDate").isJsonNull()) {
|
||||
account.setEstablishmentDate(parseDate(json.get("establishmentDate").getAsString(), dateFormat, locale));
|
||||
}
|
||||
@@ -130,13 +172,17 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
if (json.has("introducerName") && !json.get("introducerName").isJsonNull()) {
|
||||
account.setIntroducerName(json.get("introducerName").getAsString());
|
||||
}
|
||||
if (json.has("introducerCifNumber") && !json.get("introducerCifNumber").isJsonNull()) {
|
||||
account.setIntroducerCifNumber(json.get("introducerCifNumber").getAsString());
|
||||
}
|
||||
// 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());
|
||||
final String extId = json.get("externalId").getAsString().trim();
|
||||
account.setExternalId(extId.isEmpty() ? null : extId);
|
||||
}
|
||||
if (json.has("accountStatus") && !json.get("accountStatus").isJsonNull()) {
|
||||
account.setAccountStatus(json.get("accountStatus").getAsString());
|
||||
@@ -147,12 +193,24 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
}
|
||||
|
||||
// Save first to get ID
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
try {
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
} catch (RuntimeException e) {
|
||||
throwIfDuplicateExternalId(e, account.getExternalId());
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Generate account number: 00 + GL_CODE(6) + OFFICE_ID(2) + ACCOUNT_ID(5)
|
||||
final String accountNo = accountNumberGenerator.generate(account);
|
||||
account.updateAccountNo(accountNo);
|
||||
|
||||
// Save nominees if provided in payload (same transaction)
|
||||
if (json.has("nominees") && !json.get("nominees").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineesIfPresent(clientId, accountNo, "CB", json.get("nominees"));
|
||||
} else if (json.has("nominee") && !json.get("nominee").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineeIfPresent(clientId, accountNo, "CB", json.get("nominee"));
|
||||
}
|
||||
|
||||
// Facility config — UI sends "accountFacilityConfig", internal uses "facilityConfig"
|
||||
JsonObject fcJson = json.getAsJsonObject("accountFacilityConfig");
|
||||
if (fcJson == null) fcJson = json.getAsJsonObject("facilityConfig");
|
||||
@@ -170,7 +228,12 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
assemblePartners(account, json.getAsJsonArray("partners"));
|
||||
}
|
||||
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
try {
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
} catch (RuntimeException e) {
|
||||
throwIfDuplicateExternalId(e, account.getExternalId());
|
||||
throw e;
|
||||
}
|
||||
|
||||
return new CommandProcessingResultBuilder()
|
||||
.withEntityId(account.getId())
|
||||
@@ -211,8 +274,73 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
final JsonObject json = command.parsedJson().getAsJsonObject();
|
||||
LocalDate activatedOnDate = parseDate(getString(json, "activatedOnDate"), getString(json, "dateFormat"), getString(json, "locale"));
|
||||
if (activatedOnDate == null) activatedOnDate = LocalDate.now();
|
||||
// Validate minimum opening balance before activation
|
||||
java.math.BigDecimal openingBalanceValue = java.math.BigDecimal.ZERO;
|
||||
if (json.has("openingBalance") && !json.get("openingBalance").isJsonNull()) {
|
||||
try { openingBalanceValue = json.get("openingBalance").getAsBigDecimal(); } catch (Exception ignored) {}
|
||||
}
|
||||
final java.math.BigDecimal minRequired = account.getMinRequiredOpeningBalance();
|
||||
if (minRequired != null && minRequired.compareTo(java.math.BigDecimal.ZERO) > 0
|
||||
&& openingBalanceValue.compareTo(minRequired) < 0) {
|
||||
throw new org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException(
|
||||
java.util.List.of(org.apache.fineract.infrastructure.core.data.ApiParameterError.parameterError(
|
||||
"openingBalance",
|
||||
"Opening balance (" + openingBalanceValue + ") is less than minimum required opening balance (" + minRequired + ")",
|
||||
"validation.msg.currentaccount.openingBalance.less.than.min.required",
|
||||
openingBalanceValue, minRequired)));
|
||||
}
|
||||
|
||||
account.activate(activatedOnDate);
|
||||
applyFacilityChargesOnActivation(account);
|
||||
|
||||
java.math.BigDecimal currentBalance = java.math.BigDecimal.ZERO;
|
||||
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction> depositTxns = new java.util.ArrayList<>();
|
||||
final java.util.List<ChargeGlEntry> chargeEntries = new java.util.ArrayList<>();
|
||||
|
||||
// Opening balance deposit transaction
|
||||
if (openingBalanceValue.compareTo(java.math.BigDecimal.ZERO) > 0) {
|
||||
try {
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction deposit =
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction
|
||||
.deposit(account, account.getOffice(), openingBalanceValue, activatedOnDate, "OPENING-BALANCE");
|
||||
deposit.setRunningBalanceDerived(openingBalanceValue);
|
||||
account.setTotalDepositsDerived(openingBalanceValue);
|
||||
currentAccountTransactionRepository.saveAndFlush(deposit);
|
||||
currentBalance = openingBalanceValue;
|
||||
depositTxns.add(deposit);
|
||||
log.info("Opening balance deposit {} created for account id={}", openingBalanceValue, accountId);
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not process openingBalance for account id={}: {}", accountId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-deduct activation charges (chargeTimeEnum=3) from opening balance
|
||||
for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge charge : account.getCharges()) {
|
||||
if (charge.getChargeTimeEnum() != null && charge.getChargeTimeEnum() == 3
|
||||
&& charge.getAmount() != null
|
||||
&& charge.getAmount().compareTo(java.math.BigDecimal.ZERO) > 0
|
||||
&& charge.getCharge() != null) {
|
||||
charge.setAmountPaidDerived(charge.getAmount());
|
||||
charge.setAmountOutstandingDerived(java.math.BigDecimal.ZERO);
|
||||
charge.setPaid(true);
|
||||
currentBalance = currentBalance.subtract(charge.getAmount());
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction chargeTxn =
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction
|
||||
.chargeDeduction(account, account.getOffice(), charge.getAmount(), activatedOnDate);
|
||||
chargeTxn.setRunningBalanceDerived(currentBalance);
|
||||
currentAccountTransactionRepository.saveAndFlush(chargeTxn);
|
||||
chargeEntries.add(new ChargeGlEntry(chargeTxn, charge));
|
||||
log.info("Auto-deducted activation charge '{}' {} from account id={}, running balance={}",
|
||||
charge.getCharge().getName(), charge.getAmount(), accountId, currentBalance);
|
||||
}
|
||||
}
|
||||
|
||||
account.setAccountBalanceDerived(currentBalance);
|
||||
currentAccountRepository.saveAndFlush(account);
|
||||
|
||||
// Post GL journal entries — deposits in batch, each charge individually
|
||||
postGlEntries(account, depositTxns, chargeEntries);
|
||||
|
||||
return new CommandProcessingResultBuilder().withEntityId(accountId).build();
|
||||
}
|
||||
|
||||
@@ -324,6 +452,11 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
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());
|
||||
if (jo.has("sharePercentage") && !jo.get("sharePercentage").isJsonNull()) {
|
||||
try { holder.setSharePercentage(jo.get("sharePercentage").getAsBigDecimal()); } catch (Exception ignored) {}
|
||||
}
|
||||
if (jo.has("isPrimary") && !jo.get("isPrimary").isJsonNull()) holder.setPrimary(jo.get("isPrimary").getAsBoolean());
|
||||
if (jo.has("isMandatory") && !jo.get("isMandatory").isJsonNull()) holder.setMandatory(jo.get("isMandatory").getAsBoolean());
|
||||
account.getJointHolders().add(holder);
|
||||
}
|
||||
}
|
||||
@@ -337,6 +470,10 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
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());
|
||||
if (jo.has("sharePercentage") && !jo.get("sharePercentage").isJsonNull()) {
|
||||
try { partner.setSharePercentage(jo.get("sharePercentage").getAsBigDecimal()); } catch (Exception ignored) {}
|
||||
}
|
||||
if (jo.has("isMandatory") && !jo.get("isMandatory").isJsonNull()) partner.setMandatory(jo.get("isMandatory").getAsBoolean());
|
||||
account.getPartners().add(partner);
|
||||
}
|
||||
}
|
||||
@@ -377,4 +514,189 @@ public class CurrentAccountWritePlatformServiceImpl implements CurrentAccountWri
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyFacilityChargesOnActivation(final CurrentAccount account) {
|
||||
// Use account-level facility config — reflects what was selected at account opening time
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig config =
|
||||
account.getFacilityConfig();
|
||||
|
||||
log.info("[FacilityCharge] Activating account id={}, facilityConfig={}",
|
||||
account.getId(), config != null ? "FOUND" : "NULL");
|
||||
|
||||
if (config == null) {
|
||||
// Fallback: use product-level facility config
|
||||
log.info("[FacilityCharge] No account-level config, falling back to product id={}", account.getProductId());
|
||||
config = currentProductRepository.findById(account.getProductId())
|
||||
.map(p -> {
|
||||
final var pc = p.getFacilityConfig();
|
||||
if (pc == null) {
|
||||
log.info("[FacilityCharge] Product has no facility config either");
|
||||
return null;
|
||||
}
|
||||
// Map product config to account config fields inline
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig ac =
|
||||
new org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig();
|
||||
ac.setAtmFacility(Boolean.TRUE.equals(pc.getAtmFacility()));
|
||||
ac.setAtmWithdrawalFacility(Boolean.TRUE.equals(pc.getAtmWithdrawalFacility()));
|
||||
ac.setUpiFacility(Boolean.TRUE.equals(pc.getUpiFacility()));
|
||||
ac.setPosTransactionsFacility(Boolean.TRUE.equals(pc.getPosTransactionsFacility()));
|
||||
ac.setChequeBook(Boolean.TRUE.equals(pc.getChequeBook()));
|
||||
ac.setImpsFacility(Boolean.TRUE.equals(pc.getImpsFacility()));
|
||||
ac.setSmsAlerts(Boolean.TRUE.equals(pc.getSmsAlerts()));
|
||||
ac.setBbpsFacility(Boolean.TRUE.equals(pc.getBbpsFacility()));
|
||||
ac.setInsuranceCover(Boolean.TRUE.equals(pc.getInsuranceCover()));
|
||||
ac.setBepgFacility(Boolean.TRUE.equals(pc.getBepgFacility()));
|
||||
ac.setNeftFacility(Boolean.TRUE.equals(pc.getNeftFacility()));
|
||||
ac.setRtgsFacility(Boolean.TRUE.equals(pc.getRtgsFacility()));
|
||||
ac.setInterBankTotalCapFacility(Boolean.TRUE.equals(pc.getInterBankTotalCapFacility()));
|
||||
ac.setWithdrawalFacility(Boolean.TRUE.equals(pc.getWithdrawalFacility()));
|
||||
ac.setDepositFacilityKyc(Boolean.TRUE.equals(pc.getDepositFacilityKyc()));
|
||||
ac.setVirtualDebitCard(Boolean.TRUE.equals(pc.getVirtualDebitCard()));
|
||||
return ac;
|
||||
}).orElse(null);
|
||||
}
|
||||
|
||||
if (config == null) {
|
||||
log.warn("[FacilityCharge] No facility config found for account id={} — no charges applied", account.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountFacilityConfig fc = config;
|
||||
log.info("[FacilityCharge] Config found — ATM={} UPI={} NEFT={} RTGS={} CHEQUE={} SMS={} IMPs={} POS={} BBPS={} INS={} BEPG={} IBCAP={} WD={} KYC={} VCARD={} ATM_WD={}",
|
||||
fc.isAtmFacility(), fc.isUpiFacility(), fc.isNeftFacility(), fc.isRtgsFacility(),
|
||||
fc.isChequeBook(), fc.isSmsAlerts(), fc.isImpsFacility(), fc.isPosTransactionsFacility(),
|
||||
fc.isBbpsFacility(), fc.isInsuranceCover(), fc.isBepgFacility(), fc.isInterBankTotalCapFacility(),
|
||||
fc.isWithdrawalFacility(), fc.isDepositFacilityKyc(), fc.isVirtualDebitCard(), fc.isAtmWithdrawalFacility());
|
||||
applyMapped(account, fc.isAtmFacility(), "ATM_FACILITY");
|
||||
applyMapped(account, fc.isAtmWithdrawalFacility(), "ATM_WITHDRAWAL");
|
||||
applyMapped(account, fc.isUpiFacility(), "UPI_FACILITY");
|
||||
applyMapped(account, fc.isPosTransactionsFacility(), "POS_TRANSACTIONS");
|
||||
applyMapped(account, fc.isChequeBook(), "CHEQUE_BOOK");
|
||||
applyMapped(account, fc.isImpsFacility(), "IMPS_FACILITY");
|
||||
applyMapped(account, fc.isSmsAlerts(), "SMS_ALERTS");
|
||||
applyMapped(account, fc.isBbpsFacility(), "BBPS_FACILITY");
|
||||
applyMapped(account, fc.isInsuranceCover(), "INSURANCE_COVER");
|
||||
applyMapped(account, fc.isBepgFacility(), "BEPG_FACILITY");
|
||||
applyMapped(account, fc.isNeftFacility(), "NEFT_FACILITY");
|
||||
applyMapped(account, fc.isRtgsFacility(), "RTGS_FACILITY");
|
||||
applyMapped(account, fc.isInterBankTotalCapFacility(),"INTER_BANK_TOTAL_CAP");
|
||||
applyMapped(account, fc.isWithdrawalFacility(), "WITHDRAWAL_FACILITY");
|
||||
applyMapped(account, fc.isDepositFacilityKyc(), "DEPOSIT_FACILITY_KYC");
|
||||
applyMapped(account, fc.isVirtualDebitCard(), "VIRTUAL_DEBIT_CARD");
|
||||
}
|
||||
|
||||
// Wrapper to carry a charge transaction together with its charge entity for GL posting
|
||||
private record ChargeGlEntry(
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn,
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge charge) {}
|
||||
|
||||
// entity type for savings/current accounts in acc_gl_journal_entry
|
||||
private static final int ENTITY_TYPE_SAVINGS = 2;
|
||||
|
||||
private void postGlEntries(final CurrentAccount account,
|
||||
final java.util.List<org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction> depositTxns,
|
||||
final java.util.List<ChargeGlEntry> chargeEntries) {
|
||||
try {
|
||||
final CurrentProduct product = currentProductRepository.findById(account.getProductId()).orElse(null);
|
||||
if (product == null) return;
|
||||
|
||||
final Integer accountingRule = product.getAccountingType();
|
||||
if (accountingRule == null || accountingRule.equals(AccountingRuleType.NONE.getValue())) {
|
||||
log.debug("[GL] Product {} accounting=NONE — skipping", account.getProductId());
|
||||
return;
|
||||
}
|
||||
|
||||
final GLAccount cashAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.SAVINGS_REFERENCE.getValue());
|
||||
final GLAccount controlAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.SAVINGS_CONTROL.getValue());
|
||||
final GLAccount feeIncomeAccount = findGlAccount(account.getProductId(), CashAccountsForSavings.INCOME_FROM_FEES.getValue());
|
||||
|
||||
if (cashAccount == null || controlAccount == null) {
|
||||
log.warn("[GL] Missing GL account mappings for product id={} — skipping GL posting", account.getProductId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Deposit uses CA{accountId} as transaction ref (one per activation)
|
||||
final String depositRef = "CA" + account.getId();
|
||||
|
||||
// Deposits: DR SAVINGS_REFERENCE (cash in), CR SAVINGS_CONTROL (liability)
|
||||
if (depositTxns != null) {
|
||||
for (final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn : depositTxns) {
|
||||
if (txn.getId() == null || txn.getAmount() == null) continue;
|
||||
postEntry(account, cashAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), depositRef);
|
||||
postEntry(account, controlAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), depositRef);
|
||||
log.info("[GL] Deposit GL posted for account id={}, amount={}", account.getId(), txn.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
// Charges: each charge gets its own transaction ref = "CAC" + txn.getId()
|
||||
// so each charge appears as a separate transaction in journal entries
|
||||
if (chargeEntries != null && feeIncomeAccount != null) {
|
||||
for (final ChargeGlEntry entry : chargeEntries) {
|
||||
final org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountTransaction txn = entry.txn();
|
||||
if (txn.getId() == null || txn.getAmount() == null) continue;
|
||||
final String chargeRef = "CAC" + txn.getId();
|
||||
postEntry(account, controlAccount, JournalEntryType.DEBIT, txn.getAmount(), txn.getTransactionDate(), chargeRef);
|
||||
postEntry(account, feeIncomeAccount, JournalEntryType.CREDIT, txn.getAmount(), txn.getTransactionDate(), chargeRef);
|
||||
log.info("[GL] Charge GL posted for account id={}, charge={}, amount={}, ref={}", account.getId(), entry.charge().getCharge().getName(), txn.getAmount(), chargeRef);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[GL] Failed GL posting for current account id={}: {}", account.getId(), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private GLAccount findGlAccount(final Long productId, final int financialAccountType) {
|
||||
final org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping mapping =
|
||||
productToGLAccountMappingRepository.findByProductIdAndProductTypeAndFinancialAccountTypeAndPaymentTypeId(
|
||||
productId, PortfolioProductType.SAVING.getValue(), financialAccountType, null);
|
||||
return mapping != null ? mapping.getGlAccount() : null;
|
||||
}
|
||||
|
||||
private void postEntry(final CurrentAccount account, final GLAccount glAccount,
|
||||
final JournalEntryType entryType, final java.math.BigDecimal amount,
|
||||
final LocalDate date, final String transactionRef) {
|
||||
final JournalEntry entry = JournalEntry.createNew(
|
||||
account.getOffice(), null, glAccount,
|
||||
account.getCurrencyCode(), transactionRef,
|
||||
false, date, entryType, amount,
|
||||
"Current Account activation",
|
||||
ENTITY_TYPE_SAVINGS, account.getId(),
|
||||
null, null, null, null, null);
|
||||
journalEntryRepository.save(entry);
|
||||
}
|
||||
|
||||
private void throwIfDuplicateExternalId(final RuntimeException e, final String externalId) {
|
||||
Throwable cause = e;
|
||||
while (cause != null) {
|
||||
final String msg = cause.getMessage();
|
||||
if (msg != null && (msg.contains("ca_external_id_UNIQUE") || msg.contains("ca_unq_external_id"))) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.savings.account.duplicate.externalid",
|
||||
"Current Account with this External ID already exists.",
|
||||
"externalId", externalId);
|
||||
}
|
||||
cause = cause.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
private void applyMapped(final CurrentAccount account, final boolean enabled, final String facilityType) {
|
||||
if (!enabled) {
|
||||
log.debug("[FacilityCharge] {} disabled — skip", facilityType);
|
||||
return;
|
||||
}
|
||||
final var mappingOpt = facilityChargeMappingRepository.findByFacilityType(facilityType);
|
||||
if (mappingOpt.isEmpty()) {
|
||||
log.warn("[FacilityCharge] {} enabled but NO mapping found in m_facility_charge_mapping", facilityType);
|
||||
return;
|
||||
}
|
||||
final org.apache.fineract.portfolio.charge.domain.Charge charge = mappingOpt.get().getCharge();
|
||||
if (charge == null) {
|
||||
log.warn("[FacilityCharge] {} mapping exists but charge_id is NULL", facilityType);
|
||||
return;
|
||||
}
|
||||
log.info("[FacilityCharge] Applying charge '{}' (id={}) for facility {}", charge.getName(), charge.getId(), facilityType);
|
||||
account.getCharges().add(
|
||||
org.apache.fineract.portfolio.currentaccount.domain.CurrentAccountCharge
|
||||
.createFromFacility(account, charge));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ public class DepositAccountInterestRateChartReadPlatformServiceImpl implements D
|
||||
return preparedStatement;
|
||||
}, this.chartExtractor);
|
||||
if (chartDatas == null || chartDatas.isEmpty()) {
|
||||
throw new DepositAccountInterestRateChartNotFoundException(accountId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return chartDatas.iterator().next();
|
||||
|
||||
+170
-18
@@ -275,10 +275,15 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
} else if (depositAccountType.isRecurringDeposit()) {
|
||||
CalendarData calendar = this.calendarReadPlatformService.retrieveCollctionCalendarByEntity(accountId,
|
||||
CalendarEntityType.SAVINGS.getValue());
|
||||
final Integer frequency = calendar.getInterval() == -1 ? 1 : calendar.getInterval();
|
||||
final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.fromInt(calendar.getFrequency().getId().intValue());
|
||||
final PeriodFrequencyType periodFrequencyType = CalendarFrequencyType.from(calendarFrequencyType);
|
||||
final EnumOptionData frequencyType = CommonEnumerations.termFrequencyType(periodFrequencyType, "recurring.deposit.frequency.");
|
||||
Integer frequency = 1;
|
||||
EnumOptionData frequencyType = null;
|
||||
if (calendar != null) {
|
||||
frequency = calendar.getInterval() == -1 ? 1 : calendar.getInterval();
|
||||
final CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType
|
||||
.fromInt(calendar.getFrequency().getId().intValue());
|
||||
final PeriodFrequencyType periodFrequencyType = CalendarFrequencyType.from(calendarFrequencyType);
|
||||
frequencyType = CommonEnumerations.termFrequencyType(periodFrequencyType, "recurring.deposit.frequency.");
|
||||
}
|
||||
depositAccount = RecurringDepositAccountData.withInterestChartAndRecurringDetails((RecurringDepositAccountData) depositAccount,
|
||||
chart, frequency, frequencyType);
|
||||
}
|
||||
@@ -403,6 +408,24 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
periodFrequencyTypeOptions, savingsAccountDatas, maturityInstructionOptions);
|
||||
|
||||
template = FixedDepositAccountData.withInterestChart((FixedDepositAccountData) template, accountChart);
|
||||
|
||||
try {
|
||||
final Collection<CodeValueData> accountTypeOpts = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_ACCOUNT_TYPE);
|
||||
final Collection<CodeValueData> customerSegmentOpts = this.codeValueReadPlatformService
|
||||
.retrieveCodeValuesByCode(SavingsApiConstants.FD_PRODUCT_CUSTOMER_SEGMENT);
|
||||
((FixedDepositAccountData) template).setAccountTypeOptions(accountTypeOpts);
|
||||
((FixedDepositAccountData) template).setCustomerSegmentOptions(customerSegmentOpts);
|
||||
} catch (final Exception ex) {
|
||||
log.warn("Failed to load FD account type/customer segment options: {}", ex.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
final String preview = accountNumberGenerator.previewAccountNumber(productId, officeId);
|
||||
template.setPreviewAccountNo(preview);
|
||||
} catch (final Exception ex) {
|
||||
log.warn("Failed to generate preview account number for FD productId={}: {}", productId, ex.getMessage());
|
||||
}
|
||||
} else if (depositAccountType.isRecurringDeposit()) {
|
||||
// Save CBS fields from mapper result before factory methods create new objects
|
||||
final RecurringDepositAccountData rdTemplateWithCbs = (RecurringDepositAccountData) template;
|
||||
@@ -691,6 +714,16 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
selectFieldsSqlBuilder.append("sa.total_penalty_charge_derived as totalPenaltyCharge, ");
|
||||
selectFieldsSqlBuilder.append("sa.total_withhold_tax_derived as totalWithholdTax,");
|
||||
selectFieldsSqlBuilder.append("sa.deposit_type_enum as depositTypeId, ");
|
||||
selectFieldsSqlBuilder.append("sa.cbs_account_type as accountType, sa.customer_segment as customerSegment, ");
|
||||
selectFieldsSqlBuilder.append("sa.account_status as accountStatus, sa.mode_of_operation as modeOfOperation, ");
|
||||
selectFieldsSqlBuilder.append("sa.special_category as specialCategory, sa.weaker_section as weakerSection, ");
|
||||
selectFieldsSqlBuilder.append("sa.guardian_name as guardianName, sa.guardian_cif_number as guardianCifNumber, ");
|
||||
selectFieldsSqlBuilder.append("sa.guardian_relationship as guardianRelationship, sa.guardian_client_id as guardianClientId, ");
|
||||
selectFieldsSqlBuilder.append("sa.minor_date_of_birth as minorDateOfBirth, ");
|
||||
selectFieldsSqlBuilder.append("sa.introducer_client_id as introducerClientId, sa.introducer_name as introducerName, ");
|
||||
selectFieldsSqlBuilder.append("sa.introducer_cif_number as introducerCifNumber, ");
|
||||
selectFieldsSqlBuilder.append("sa.linked_account_id as linkedAccountId, sa.linked_account_type as linkedAccountType, ");
|
||||
selectFieldsSqlBuilder.append("sa.linked_account_no as linkedAccountNo, ");
|
||||
selectFieldsSqlBuilder.append("sa.min_balance_for_interest_calculation as minBalanceForInterestCalculation, ");
|
||||
selectFieldsSqlBuilder.append("sa.withhold_tax as withHoldTax,");
|
||||
selectFieldsSqlBuilder.append("tg.id as taxGroupId, tg.name as taxGroupName ");
|
||||
@@ -701,6 +734,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
selectTablesSqlBuilder.append("from m_savings_account sa ");
|
||||
selectTablesSqlBuilder.append("left join m_deposit_account_term_and_preclosure datp on sa.id = datp.savings_account_id ");
|
||||
selectTablesSqlBuilder.append("join m_savings_product sp ON sa.product_id = sp.id ");
|
||||
selectTablesSqlBuilder.append("left join m_code_value icv on icv.id = sp.interest_calculate_selector ");
|
||||
selectTablesSqlBuilder.append("join m_currency curr on curr.code = sa.currency_code ");
|
||||
selectTablesSqlBuilder.append("left join m_client c ON c.id = sa.client_id ");
|
||||
selectTablesSqlBuilder.append("left join m_group g ON g.id = sa.group_id ");
|
||||
@@ -709,7 +743,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
selectTablesSqlBuilder.append("left join m_appuser rbu on rbu.id = sa.rejectedon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_appuser wbu on wbu.id = sa.withdrawnon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_appuser abu on abu.id = sa.approvedon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_appuser avbu on rbu.id = sa.activatedon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_appuser avbu on avbu.id = sa.activatedon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_appuser cbu on cbu.id = sa.closedon_userid ");
|
||||
selectTablesSqlBuilder.append("left join m_tax_group tg on tg.id = sa.tax_group_id ");
|
||||
|
||||
@@ -899,7 +933,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
|
||||
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, ");
|
||||
sqlBuilder.append("datp.transfer_interest_to_linked_account as transferInterestToSavings, ");
|
||||
sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId ");
|
||||
sqlBuilder.append("datp.transfer_to_savings_account_id as transferToSavingsId, ");
|
||||
sqlBuilder.append("sp.fd_type as fdType, sp.product_code as productCode, sp.effective_date as effectiveDate, ");
|
||||
sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, ");
|
||||
sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, ");
|
||||
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
|
||||
sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
|
||||
sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, ");
|
||||
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, ");
|
||||
sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, ");
|
||||
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, ");
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
|
||||
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
|
||||
sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, ");
|
||||
sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, ");
|
||||
sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, ");
|
||||
sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
|
||||
sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, ");
|
||||
sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
|
||||
sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate ");
|
||||
|
||||
sqlBuilder.append(super.selectTablesSql());
|
||||
|
||||
@@ -949,10 +1004,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
|
||||
final Long transferToSavingsId = JdbcSupport.getLong(rs, TRANSFER_TO_SAVINGS_ID);
|
||||
|
||||
return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
|
||||
final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
|
||||
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
|
||||
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
|
||||
depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, transferToSavingsId);
|
||||
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"),
|
||||
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
|
||||
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
|
||||
JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"),
|
||||
JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
|
||||
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
|
||||
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
|
||||
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
|
||||
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
|
||||
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
|
||||
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
|
||||
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
|
||||
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1009,7 +1080,28 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
sqlBuilder.append("datp.maturity_date as maturityDate, ");
|
||||
sqlBuilder.append("datp.deposit_period as depositPeriod, ");
|
||||
sqlBuilder.append("datp.deposit_period_frequency_enum as depositPeriodFrequencyTypeId, ");
|
||||
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId ");
|
||||
sqlBuilder.append("datp.on_account_closure_enum as onAccountClosureId, ");
|
||||
sqlBuilder.append("sp.product_code as productCode, sp.effective_date as effectiveDate, ");
|
||||
sqlBuilder.append("sp.effective_to as effectiveTo, sp.end_date as endDate, sp.introducer_required as introducerRequired, ");
|
||||
sqlBuilder.append("sp.share_linking_enabled as shareLinkingEnabled, ");
|
||||
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
|
||||
sqlBuilder.append("sp.dormancy_alert as dormancyAlert, sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
|
||||
sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, ");
|
||||
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, sp.interest_calculate_selector as interestCalculateSelector, ");
|
||||
sqlBuilder.append("icv.code_value as interestCalculationTypeName, sp.average_period as averagePeriod, ");
|
||||
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, ");
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, sp.msme as msme, ");
|
||||
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
|
||||
sqlBuilder.append("sp.loan_facility as loanFacility, sp.premature_withdrawal_option as prematureWithdrawalOption, ");
|
||||
sqlBuilder.append("sp.tenure_flexibility as tenureFlexibility, sp.lien_marked as lienMarked, ");
|
||||
sqlBuilder.append("sp.maturity_instructions as maturityInstructions, sp.credit_maturity_proceeds as creditMaturityProceeds, ");
|
||||
sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
|
||||
sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, ");
|
||||
sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
|
||||
sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate ");
|
||||
|
||||
sqlBuilder.append(this.selectTablesSql());
|
||||
sqlBuilder.append("left join m_deposit_account_recurring_detail dard on sa.id = dard.savings_account_id ");
|
||||
@@ -1066,12 +1158,30 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
: SavingsEnumerations.depositAccountOnClosureType(onAccountClosureId);
|
||||
final LocalDate expectedFirstDepositOnDate = JdbcSupport.getLocalDate(rs, EXPECTED_FIRST_DEPOSIT_ON_DATE);
|
||||
|
||||
return RecurringDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
|
||||
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
|
||||
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
|
||||
depositPeriodFrequencyType, mandatoryRecommendedDepositAmount, onAccountClosureType, expectedFirstDepositOnDate,
|
||||
totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit, allowWithdrawal, adjustAdvanceTowardsFuturePayments,
|
||||
isCalendarInherited);
|
||||
final RecurringDepositAccountData result = RecurringDepositAccountData.instance(depositAccountData,
|
||||
preClosurePenalApplicable, preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm,
|
||||
minDepositTermType, maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount,
|
||||
maturityAmount, maturityDate, depositPeriod, depositPeriodFrequencyType, mandatoryRecommendedDepositAmount,
|
||||
onAccountClosureType, expectedFirstDepositOnDate, totalOverdueAmount, noOfOverdueInstallments, isMandatoryDeposit,
|
||||
allowWithdrawal, adjustAdvanceTowardsFuturePayments, isCalendarInherited);
|
||||
|
||||
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("productCode"),
|
||||
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
|
||||
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
|
||||
JdbcSupport.getInteger(rs, "daysToInactive"), JdbcSupport.getInteger(rs, "daysToDormancy"),
|
||||
JdbcSupport.getInteger(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
|
||||
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
|
||||
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
|
||||
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
|
||||
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
|
||||
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
|
||||
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
|
||||
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
|
||||
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1418,6 +1528,31 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
sqlBuilder.append(", dptp.pre_closure_penal_applicable as preClosurePenalApplicable, ");
|
||||
sqlBuilder.append("dptp.pre_closure_penal_interest as preClosurePenalInterest, ");
|
||||
sqlBuilder.append("dptp.pre_closure_penal_interest_on_enum as preClosurePenalInterestOnId, ");
|
||||
sqlBuilder.append("sa.account_type as accountType, sa.customer_segment as customerSegment, sa.fd_type as fdType, ");
|
||||
sqlBuilder.append("sa.product_code as productCode, ");
|
||||
sqlBuilder.append("sa.effective_date as effectiveDate, sa.effective_to as effectiveTo, sa.end_date as endDate, ");
|
||||
sqlBuilder.append("sa.introducer_required as introducerRequired, sa.share_linking_enabled as shareLinkingEnabled, ");
|
||||
sqlBuilder.append("sa.nomination_facility as nominationFacility, sa.dormancy_alert as dormancyAlert, ");
|
||||
sqlBuilder.append("sa.is_dormancy_tracking_active as isDormancyTrackingActive, ");
|
||||
sqlBuilder.append("sa.days_to_inactive as daysToInactive, sa.days_to_dormancy as daysToDormancy, ");
|
||||
sqlBuilder.append("sa.days_to_escheat as daysToEscheat, ");
|
||||
sqlBuilder.append("sa.interest_calculate_selector as interestCalculateSelector, ");
|
||||
sqlBuilder.append("icv.code_value as interestCalculationTypeName, ");
|
||||
sqlBuilder.append("sa.average_period as averagePeriod, ");
|
||||
sqlBuilder.append("sa.auto_post as autoPost, sa.frequency_fix_day as frequencyFixDay, ");
|
||||
sqlBuilder.append("sa.last_interest_date as lastInterestDate, sa.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sa.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sa.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sa.balance_swap_option as balanceSwapOption, sa.msme as msme, ");
|
||||
sqlBuilder.append("sa.auto_debit_facility as autoDebitFacility, sa.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sa.premature_withdrawal_option as prematureWithdrawalOption, sa.tenure_flexibility as tenureFlexibility, ");
|
||||
sqlBuilder.append("sa.lien_marked as lienMarked, sa.maturity_instructions as maturityInstructions, ");
|
||||
sqlBuilder.append("sa.credit_maturity_proceeds as creditMaturityProceeds, ");
|
||||
sqlBuilder.append("sa.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
|
||||
sqlBuilder.append("sa.panel_request_applicable as panelRequestApplicable, ");
|
||||
sqlBuilder.append("sa.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
|
||||
sqlBuilder.append("sa.deceased_settlement as deceasedSettlement, ");
|
||||
sqlBuilder.append("sa.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, ");
|
||||
sqlBuilder.append("dptp.min_deposit_term as minDepositTerm, ");
|
||||
sqlBuilder.append("dptp.max_deposit_term as maxDepositTerm, ");
|
||||
sqlBuilder.append("dptp.min_deposit_term_type_enum as minDepositTermTypeId, ");
|
||||
@@ -1426,6 +1561,7 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId ");
|
||||
|
||||
sqlBuilder.append(super.selectTablesSql());
|
||||
sqlBuilder.append("left join m_code_value icv on icv.id = sa.interest_calculate_selector ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
@@ -1467,10 +1603,26 @@ public class DepositAccountReadPlatformServiceImpl implements DepositAccountRead
|
||||
final EnumOptionData onAccountClosureType = null;
|
||||
final Boolean transferInterestToSavings = false;
|
||||
|
||||
return FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable, preClosurePenalInterest,
|
||||
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
|
||||
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount, maturityDate, depositPeriod,
|
||||
depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null);
|
||||
final FixedDepositAccountData result = FixedDepositAccountData.instance(depositAccountData, preClosurePenalApplicable,
|
||||
preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType,
|
||||
maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, depositAmount, maturityAmount,
|
||||
maturityDate, depositPeriod, depositPeriodFrequencyType, onAccountClosureType, transferInterestToSavings, null);
|
||||
result.setCbsProductFields(rs.getString("accountType"), rs.getString("customerSegment"), rs.getString("fdType"), rs.getString("productCode"),
|
||||
JdbcSupport.getLocalDate(rs, "effectiveDate"), JdbcSupport.getLocalDate(rs, "effectiveTo"),
|
||||
JdbcSupport.getLocalDate(rs, "endDate"), rs.getBoolean("introducerRequired"), rs.getBoolean("shareLinkingEnabled"),
|
||||
rs.getBoolean("nominationFacility"), rs.getBoolean("dormancyAlert"), rs.getBoolean("isDormancyTrackingActive"),
|
||||
JdbcSupport.getLong(rs, "daysToInactive"), JdbcSupport.getLong(rs, "daysToDormancy"),
|
||||
JdbcSupport.getLong(rs, "daysToEscheat"), JdbcSupport.getInteger(rs, "interestCalculateSelector"),
|
||||
rs.getString("interestCalculationTypeName"), rs.getString("averagePeriod"), rs.getBoolean("autoPost"),
|
||||
JdbcSupport.getInteger(rs, "frequencyFixDay"), JdbcSupport.getLocalDate(rs, "lastInterestDate"),
|
||||
rs.getBoolean("enableLockinPeriod"), rs.getString("balanceFrequency"),
|
||||
rs.getBoolean("accountClosedZeroBalanceInDayClosing"), rs.getBoolean("balanceSwapOption"), rs.getBoolean("msme"),
|
||||
rs.getBoolean("autoDebitFacility"), rs.getBoolean("loanFacility"), rs.getBoolean("prematureWithdrawalOption"),
|
||||
rs.getBoolean("tenureFlexibility"), rs.getBoolean("lienMarked"), rs.getBoolean("maturityInstructions"),
|
||||
rs.getBoolean("creditMaturityProceeds"), rs.getBoolean("penaltyGracePeriodMissedInstallments"),
|
||||
rs.getBoolean("panelRequestApplicable"), rs.getBoolean("noPenaltyNomineeHeirClosure"),
|
||||
rs.getBoolean("deceasedSettlement"), rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -197,8 +197,6 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth, postInterestOnDate, false, postReversals);
|
||||
}
|
||||
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
final boolean isPreMatureClosure = false;
|
||||
@@ -287,7 +285,6 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo
|
||||
fromSavingsAccount, isRegularTransaction, isExceptionForBalanceCheck);
|
||||
this.accountTransfersWritePlatformService.transferFunds(accountTransferDTO);
|
||||
}
|
||||
updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
|
||||
+71
-4
@@ -48,6 +48,7 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.deposit.FixedDepositAccountCreateBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.deposit.RecurringDepositAccountCreateBusinessEvent;
|
||||
@@ -69,6 +70,7 @@ import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.client.service.ClientNomineeWritePlatformService;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepository;
|
||||
@@ -89,6 +91,7 @@ import org.apache.fineract.portfolio.savings.domain.RecurringDepositAccountRepos
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccount;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountCharge;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountChargeAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProduct;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
|
||||
@@ -121,6 +124,8 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
private final ConfigurationDomainService configurationDomainService;
|
||||
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final SavingsAccountAssembler savingsAccountAssembler;
|
||||
private final ClientNomineeWritePlatformService clientNomineeWritePlatformService;
|
||||
|
||||
/*
|
||||
* Guaranteed to throw an exception no matter what the data integrity issue is.
|
||||
@@ -130,16 +135,16 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
String msg = "Unknown data integrity issue with savings account.";
|
||||
String param = null;
|
||||
Object[] msgArgs;
|
||||
Throwable checkEx = realCause == null ? dve : realCause;
|
||||
if (checkEx.getMessage().contains("sa_account_no_UNIQUE")) {
|
||||
final String causeMsg = getAllCauseMessages(realCause, dve);
|
||||
if (causeMsg.contains("sa_account_no_UNIQUE")) {
|
||||
final String accountNo = command.stringValueOfParameterNamed("accountNo");
|
||||
msgCode += ".duplicate.accountNo";
|
||||
msg = "Savings account with accountNo " + accountNo + " already exists";
|
||||
param = "accountNo";
|
||||
msgArgs = new Object[] { accountNo, dve };
|
||||
} else if (checkEx.getMessage().contains("sa_external_id_UNIQUE")) {
|
||||
} else if (causeMsg.contains("sa_external_id_UNIQUE") || causeMsg.contains("m_savings_account_external_id_key")) {
|
||||
final String externalId = command.stringValueOfParameterNamed("externalId");
|
||||
msgCode += ".duplicate.externalId";
|
||||
msgCode = "error.msg.savingsaccount.duplicate.externalId";
|
||||
msg = "Savings account with externalId " + externalId + " already exists";
|
||||
param = "externalId";
|
||||
msgArgs = new Object[] { externalId, dve };
|
||||
@@ -151,6 +156,18 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
throw ErrorHandler.getMappable(dve, msgCode, msg, param, msgArgs);
|
||||
}
|
||||
|
||||
private String getAllCauseMessages(final Throwable realCause, final Exception dve) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
Throwable t = realCause != null ? realCause : dve;
|
||||
while (t != null) {
|
||||
if (t.getMessage() != null) {
|
||||
sb.append(t.getMessage()).append(' ');
|
||||
}
|
||||
t = t.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult submitFDApplication(final JsonCommand command) {
|
||||
@@ -165,11 +182,25 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
final FixedDepositAccount account = (FixedDepositAccount) this.depositAccountAssembler.assembleFrom(command, submittedBy,
|
||||
DepositAccountType.FIXED_DEPOSIT);
|
||||
|
||||
if (account.getClient() != null && account.productId() != null) {
|
||||
long existingCount = this.savingAccountRepository.countActiveAccountsByClientAndProduct(
|
||||
account.getClient().getId(), account.productId());
|
||||
if (existingCount > 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.savings.account.duplicate.product.for.client",
|
||||
"Client already has an active Fixed Deposit account with the same product.",
|
||||
"productId", account.productId());
|
||||
}
|
||||
}
|
||||
|
||||
final MathContext mc = MathContext.DECIMAL64;
|
||||
final boolean isPreMatureClosure = false;
|
||||
|
||||
account.updateMaturityDateAndAmountBeforeAccountActivation(mc, isPreMatureClosure, isSavingsInterestPostingAtCurrentPeriodEnd,
|
||||
financialYearBeginningMonth);
|
||||
|
||||
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
|
||||
|
||||
this.fixedDepositAccountRepository.saveAndFlush(account);
|
||||
|
||||
if (account.isAccountNumberRequiresAutoGeneration()) {
|
||||
@@ -191,6 +222,16 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
this.accountAssociationsRepository.save(accountAssociations);
|
||||
}
|
||||
|
||||
// Save nominees if provided in payload (same transaction)
|
||||
final com.google.gson.JsonObject fdRoot = command.parsedJson().getAsJsonObject();
|
||||
if (fdRoot.has("nominees") && !fdRoot.get("nominees").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineesIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "FD", fdRoot.get("nominees"));
|
||||
} else if (fdRoot.has("nominee") && !fdRoot.get("nominee").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineeIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "FD", fdRoot.get("nominee"));
|
||||
}
|
||||
|
||||
final Long savingsId = account.getId();
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new FixedDepositAccountCreateBusinessEvent(account));
|
||||
|
||||
@@ -226,6 +267,19 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
final RecurringDepositAccount account = (RecurringDepositAccount) this.depositAccountAssembler.assembleFrom(command,
|
||||
submittedBy, DepositAccountType.RECURRING_DEPOSIT);
|
||||
|
||||
if (account.getClient() != null && account.productId() != null) {
|
||||
long existingCount = this.savingAccountRepository.countActiveAccountsByClientAndProduct(
|
||||
account.getClient().getId(), account.productId());
|
||||
if (existingCount > 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.savings.account.duplicate.product.for.client",
|
||||
"Client already has an active Recurring Deposit account with the same product.",
|
||||
"productId", account.productId());
|
||||
}
|
||||
}
|
||||
|
||||
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
|
||||
|
||||
this.recurringDepositAccountRepository.save(account);
|
||||
|
||||
if (account.isAccountNumberRequiresAutoGeneration()) {
|
||||
@@ -251,6 +305,17 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
financialYearBeginningMonth);
|
||||
account.validateApplicableInterestRate();
|
||||
savingAccountRepository.save(account);
|
||||
|
||||
// Save nominees if provided in payload (same transaction)
|
||||
final com.google.gson.JsonObject rdRoot = command.parsedJson().getAsJsonObject();
|
||||
if (rdRoot.has("nominees") && !rdRoot.get("nominees").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineesIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "RD", rdRoot.get("nominees"));
|
||||
} else if (rdRoot.has("nominee") && !rdRoot.get("nominee").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineeIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "RD", rdRoot.get("nominee"));
|
||||
}
|
||||
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new RecurringDepositAccountCreateBusinessEvent(account));
|
||||
|
||||
return new CommandProcessingResultBuilder() //
|
||||
@@ -346,6 +411,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
DepositAccountType.FIXED_DEPOSIT);
|
||||
checkClientOrGroupActive(account);
|
||||
account.modifyApplication(command, changes);
|
||||
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
|
||||
account.validateNewApplicationState(DepositAccountType.FIXED_DEPOSIT.resourceName());
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
@@ -436,6 +502,7 @@ public class DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
DepositAccountType.RECURRING_DEPOSIT);
|
||||
checkClientOrGroupActive(account);
|
||||
account.modifyApplication(command, changes);
|
||||
this.savingsAccountAssembler.applyCbsFields(command, command.parsedJson(), account);
|
||||
account.validateNewApplicationState(DepositAccountType.RECURRING_DEPOSIT.resourceName());
|
||||
|
||||
if (!changes.isEmpty()) {
|
||||
|
||||
+77
-5
@@ -230,11 +230,37 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
sqlBuilder.append("dptp.in_multiples_of_deposit_term as inMultiplesOfDepositTerm, ");
|
||||
sqlBuilder.append("dptp.in_multiples_of_deposit_term_type_enum as inMultiplesOfDepositTermTypeId, ");
|
||||
sqlBuilder.append("dptp.min_deposit_amount as minDepositAmount, dptp.deposit_amount as depositAmount, ");
|
||||
sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount ");
|
||||
sqlBuilder.append("dptp.max_deposit_amount as maxDepositAmount, ");
|
||||
sqlBuilder.append("sp.account_type as accountType, sp.fd_type as fdType, sp.product_code as productCode, ");
|
||||
sqlBuilder.append("sp.customer_segment as customerSegment, ");
|
||||
sqlBuilder.append("sp.gl_account_id as glAccountId, gla.name as glAccountName, ");
|
||||
sqlBuilder.append("sp.effective_date as effectiveDate, sp.effective_to as effectiveTo, sp.end_date as endDate, ");
|
||||
sqlBuilder.append("sp.introducer_required as introducerRequired, sp.share_linking_enabled as shareLinkingEnabled, ");
|
||||
sqlBuilder.append("sp.nomination_facility as nominationFacility, sp.dormancy_alert as dormancyAlert, ");
|
||||
sqlBuilder.append("sp.is_dormancy_tracking_active as isDormancyTrackingActive, ");
|
||||
sqlBuilder.append("sp.days_to_inactive as daysToInactive, sp.days_to_dormancy as daysToDormancy, ");
|
||||
sqlBuilder.append("sp.days_to_escheat as daysToEscheat, ");
|
||||
sqlBuilder.append("sp.interest_calculate_selector as interestCalculateSelector, ");
|
||||
sqlBuilder.append("sp.average_period as averagePeriod, ");
|
||||
sqlBuilder.append("sp.auto_post as autoPost, sp.frequency_fix_day as frequencyFixDay, ");
|
||||
sqlBuilder.append("sp.last_interest_date as lastInterestDate, sp.enable_lockin_period as enableLockinPeriod, ");
|
||||
sqlBuilder.append("sp.balance_frequency as balanceFrequency, ");
|
||||
sqlBuilder.append("sp.account_closed_zero_balance_in_day_closing as accountClosedZeroBalanceInDayClosing, ");
|
||||
sqlBuilder.append("sp.auto_debit_facility as autoDebitFacility, sp.loan_facility as loanFacility, ");
|
||||
sqlBuilder.append("sp.premature_withdrawal_option as prematureWithdrawalOption, sp.tenure_flexibility as tenureFlexibility, ");
|
||||
sqlBuilder.append("sp.lien_marked as lienMarked, sp.maturity_instructions as maturityInstructions, ");
|
||||
sqlBuilder.append("sp.credit_maturity_proceeds as creditMaturityProceeds, ");
|
||||
sqlBuilder.append("sp.penalty_grace_period_missed_installments as penaltyGracePeriodMissedInstallments, ");
|
||||
sqlBuilder.append("sp.panel_request_applicable as panelRequestApplicable, ");
|
||||
sqlBuilder.append("sp.no_penalty_nominee_heir_closure as noPenaltyNomineeHeirClosure, ");
|
||||
sqlBuilder.append("sp.deceased_settlement as deceasedSettlement, ");
|
||||
sqlBuilder.append("sp.allow_debit_txn_after_due_date as allowDebitTxnAfterDueDate, ");
|
||||
sqlBuilder.append("sp.withdrawal_fee_for_transfer as withdrawalFeeForTransfers ");
|
||||
sqlBuilder.append("from m_savings_product sp ");
|
||||
sqlBuilder.append("left join m_deposit_product_term_and_preclosure dptp on sp.id=dptp.savings_product_id ");
|
||||
sqlBuilder.append("join m_currency curr on curr.code = sp.currency_code ");
|
||||
sqlBuilder.append(" left join m_tax_group tg on tg.id = sp.tax_group_id ");
|
||||
sqlBuilder.append("left join m_tax_group tg on tg.id = sp.tax_group_id ");
|
||||
sqlBuilder.append("left join acc_gl_account gla on gla.id = sp.gl_account_id ");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
@@ -271,9 +297,55 @@ public class DepositProductReadPlatformServiceImpl implements DepositProductRead
|
||||
final BigDecimal depositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "depositAmount");
|
||||
final BigDecimal maxDepositAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "maxDepositAmount");
|
||||
|
||||
return FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable, preClosurePenalInterest,
|
||||
preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType, maxDepositTermType,
|
||||
inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount, maxDepositAmount);
|
||||
final FixedDepositProductData result = FixedDepositProductData.instance(depositProductData, preClosurePenalApplicable,
|
||||
preClosurePenalInterest, preClosurePenalInterestOnType, minDepositTerm, maxDepositTerm, minDepositTermType,
|
||||
maxDepositTermType, inMultiplesOfDepositTerm, inMultiplesOfDepositTermType, minDepositAmount, depositAmount,
|
||||
maxDepositAmount);
|
||||
|
||||
// CBS Custom Fields
|
||||
result.setAccountType(rs.getString("accountType"));
|
||||
result.setFdType(rs.getString("fdType"));
|
||||
result.setProductCode(rs.getString("productCode"));
|
||||
result.setCustomerSegment(rs.getString("customerSegment"));
|
||||
result.setGlAccountId(JdbcSupport.getLong(rs, "glAccountId"));
|
||||
result.setGlAccountName(rs.getString("glAccountName"));
|
||||
final java.time.LocalDate effectiveDate = JdbcSupport.getLocalDate(rs, "effectiveDate");
|
||||
result.setEffectiveDate(effectiveDate != null ? effectiveDate.toString() : null);
|
||||
final java.time.LocalDate effectiveTo = JdbcSupport.getLocalDate(rs, "effectiveTo");
|
||||
result.setEffectiveTo(effectiveTo != null ? effectiveTo.toString() : null);
|
||||
final java.time.LocalDate endDate = JdbcSupport.getLocalDate(rs, "endDate");
|
||||
result.setEndDate(endDate != null ? endDate.toString() : null);
|
||||
result.setIntroducerRequired(rs.getBoolean("introducerRequired"));
|
||||
result.setShareLinkingEnabled(rs.getBoolean("shareLinkingEnabled"));
|
||||
result.setNominationFacility(rs.getBoolean("nominationFacility"));
|
||||
result.setDormancyAlert(rs.getBoolean("dormancyAlert"));
|
||||
result.setIsDormancyTrackingActive(rs.getBoolean("isDormancyTrackingActive"));
|
||||
result.setDaysToInactive(JdbcSupport.getLong(rs, "daysToInactive"));
|
||||
result.setDaysToDormancy(JdbcSupport.getLong(rs, "daysToDormancy"));
|
||||
result.setDaysToEscheat(JdbcSupport.getLong(rs, "daysToEscheat"));
|
||||
result.setInterestCalculateSelector(JdbcSupport.getInteger(rs, "interestCalculateSelector"));
|
||||
result.setAveragePeriod(rs.getString("averagePeriod"));
|
||||
result.setAutoPost(rs.getBoolean("autoPost"));
|
||||
result.setFrequencyFixDay(JdbcSupport.getInteger(rs, "frequencyFixDay"));
|
||||
final java.time.LocalDate lastInterestDate = JdbcSupport.getLocalDate(rs, "lastInterestDate");
|
||||
result.setLastInterestDate(lastInterestDate != null ? lastInterestDate.toString() : null);
|
||||
result.setEnableLockinPeriod(rs.getBoolean("enableLockinPeriod"));
|
||||
result.setBalanceFrequency(rs.getString("balanceFrequency"));
|
||||
result.setAccountClosedZeroBalanceInDayClosing(rs.getBoolean("accountClosedZeroBalanceInDayClosing"));
|
||||
result.setAutoDebitFacility(rs.getBoolean("autoDebitFacility"));
|
||||
result.setLoanFacility(rs.getBoolean("loanFacility"));
|
||||
result.setPrematureWithdrawalOption(rs.getBoolean("prematureWithdrawalOption"));
|
||||
result.setTenureFlexibility(rs.getBoolean("tenureFlexibility"));
|
||||
result.setLienMarked(rs.getBoolean("lienMarked"));
|
||||
result.setMaturityInstructions(rs.getBoolean("maturityInstructions"));
|
||||
result.setCreditMaturityProceeds(rs.getBoolean("creditMaturityProceeds"));
|
||||
result.setPenaltyGracePeriodMissedInstallments(rs.getBoolean("penaltyGracePeriodMissedInstallments"));
|
||||
result.setPanelRequestApplicable(rs.getBoolean("panelRequestApplicable"));
|
||||
result.setNoPenaltyNomineeHeirClosure(rs.getBoolean("noPenaltyNomineeHeirClosure"));
|
||||
result.setDeceasedSettlement(rs.getBoolean("deceasedSettlement"));
|
||||
result.setAllowDebitTxnAfterDueDate(rs.getBoolean("allowDebitTxnAfterDueDate"));
|
||||
result.setWithdrawalFeeForTransfers(rs.getBoolean("withdrawalFeeForTransfers"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||
|
||||
public interface FdBaseInterestRateReadPlatformService {
|
||||
|
||||
List<FdInterestRateEffectiveData> retrieveAllInterestRates();
|
||||
|
||||
List<FdInterestRateEffectiveData> retrieveAllActiveInterestRates();
|
||||
|
||||
List<FdInterestRateEffectiveData> retrieveActiveInterestRatesByType(String fdType);
|
||||
|
||||
FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById);
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.portfolio.savings.api.FdInterestRateSaveRequest;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateEffectiveData;
|
||||
import org.apache.fineract.portfolio.savings.data.FdInterestRateSlabData;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffective;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistory;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FdBaseInterestRateReadPlatformServiceImpl implements FdBaseInterestRateReadPlatformService {
|
||||
|
||||
private final FdBaseInterestRateEffectiveRepository effectiveRepository;
|
||||
private final FdBaseInterestRateSlabHistoryRepository slabRepository;
|
||||
|
||||
@Override
|
||||
public List<FdInterestRateEffectiveData> retrieveAllInterestRates() {
|
||||
return effectiveRepository.findByOrderByEffectiveFromDateDesc().stream().map(this::toData).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FdInterestRateEffectiveData> retrieveAllActiveInterestRates() {
|
||||
return effectiveRepository.findByIsActiveTrueOrderByEffectiveFromDateDesc().stream().map(this::toData)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<FdInterestRateEffectiveData> retrieveActiveInterestRatesByType(String fdType) {
|
||||
return effectiveRepository.findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType).stream().map(this::toData)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public FdInterestRateEffectiveData saveInterestRates(FdInterestRateSaveRequest request, Long createdById) {
|
||||
final String fdType = request.getFdType();
|
||||
final LocalDate today = LocalDate.now();
|
||||
|
||||
final List<FdBaseInterestRateEffective> activeVersions = effectiveRepository
|
||||
.findByFdTypeAndIsActiveTrueOrderByEffectiveFromDateDesc(fdType);
|
||||
for (FdBaseInterestRateEffective version : activeVersions) {
|
||||
version.deactivate(today);
|
||||
}
|
||||
if (!activeVersions.isEmpty()) {
|
||||
effectiveRepository.saveAll(activeVersions);
|
||||
}
|
||||
|
||||
final String label = request.getVersionLabel() != null && !request.getVersionLabel().isBlank()
|
||||
? request.getVersionLabel()
|
||||
: "FD " + fdType + " Rate Version " + LocalDate.now();
|
||||
|
||||
FdBaseInterestRateEffective newVersion = FdBaseInterestRateEffective.create(fdType, label, today, request.getRemarks(), createdById);
|
||||
newVersion = effectiveRepository.saveAndFlush(newVersion);
|
||||
|
||||
final Long versionId = newVersion.getId();
|
||||
int srCounter = 1;
|
||||
if (request.getSlabs() != null) {
|
||||
for (FdInterestRateSaveRequest.SlabRequest slabRequest : request.getSlabs()) {
|
||||
final int sr = slabRequest.getSrNo() != null ? slabRequest.getSrNo() : srCounter++;
|
||||
final FdBaseInterestRateSlabHistory slab = FdBaseInterestRateSlabHistory.create(versionId, sr,
|
||||
slabRequest.getDescription(), slabRequest.getPeriodTypeEnum(), slabRequest.getFromPeriod(),
|
||||
slabRequest.getToPeriod(), slabRequest.getAnnualInterestRate(), slabRequest.getSeniorCitizenRate());
|
||||
slabRepository.save(slab);
|
||||
}
|
||||
}
|
||||
|
||||
return toData(newVersion);
|
||||
}
|
||||
|
||||
private FdInterestRateEffectiveData toData(FdBaseInterestRateEffective e) {
|
||||
List<FdInterestRateSlabData> slabs = slabRepository.findByEffectiveIdOrderBySrNo(e.getId()).stream()
|
||||
.map(s -> FdInterestRateSlabData.builder()
|
||||
.id(s.getId())
|
||||
.srNo(s.getSrNo())
|
||||
.description(s.getDescription())
|
||||
.periodTypeEnum(s.getPeriodTypeEnum())
|
||||
.periodTypeLabel(periodLabel(s.getPeriodTypeEnum()))
|
||||
.fromPeriod(s.getFromPeriod())
|
||||
.toPeriod(s.getToPeriod())
|
||||
.annualInterestRate(s.getAnnualInterestRate())
|
||||
.seniorCitizenRate(s.getSeniorCitizenRate())
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return FdInterestRateEffectiveData.builder()
|
||||
.id(e.getId())
|
||||
.fdType(e.getFdType())
|
||||
.versionLabel(e.getVersionLabel())
|
||||
.effectiveFromDate(e.getEffectiveFromDate())
|
||||
.effectiveToDate(e.getEffectiveToDate())
|
||||
.isActive(e.getIsActive())
|
||||
.remarks(e.getRemarks())
|
||||
.slabs(slabs)
|
||||
.build();
|
||||
}
|
||||
|
||||
private String periodLabel(Integer code) {
|
||||
if (code == null) {
|
||||
return "Days";
|
||||
}
|
||||
|
||||
return switch (code) {
|
||||
case 0 -> "Days";
|
||||
case 1 -> "Weeks";
|
||||
case 2 -> "Months";
|
||||
case 3 -> "Years";
|
||||
default -> "Days";
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
||||
|
||||
public interface RecurringDepositAccountInterestCalculationService {
|
||||
|
||||
HashMap<String, Object> calculateInterest(JsonQuery query);
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.fineract.portfolio.savings.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.annualInterestRateParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.depositAmountParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.interestCompoundingPeriodInMonthsParamName;
|
||||
import static org.apache.fineract.portfolio.savings.DepositsApiConstants.tenureInMonthsParamName;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonQuery;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class RecurringDepositAccountInterestCalculationServiceImpl implements RecurringDepositAccountInterestCalculationService {
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
@Override
|
||||
public HashMap<String, Object> calculateInterest(JsonQuery query) {
|
||||
JsonElement element = query.parsedJson();
|
||||
BigDecimal depositAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(depositAmountParamName, element);
|
||||
BigDecimal annualInterestRate = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(annualInterestRateParamName, element);
|
||||
Long tenureInMonths = this.fromApiJsonHelper.extractLongNamed(tenureInMonthsParamName, element);
|
||||
Long interestCompoundingPeriodInMonths = this.fromApiJsonHelper.extractLongNamed(interestCompoundingPeriodInMonthsParamName,
|
||||
element);
|
||||
LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
|
||||
if (submittedOnDate == null) {
|
||||
submittedOnDate = LocalDate.now();
|
||||
}
|
||||
|
||||
BigDecimal maturityAmount = this.calculateInterestInternal(depositAmount, annualInterestRate, tenureInMonths,
|
||||
interestCompoundingPeriodInMonths, submittedOnDate);
|
||||
|
||||
BigDecimal totalPrincipal = depositAmount.multiply(BigDecimal.valueOf(tenureInMonths));
|
||||
BigDecimal totalInterest = maturityAmount.subtract(totalPrincipal);
|
||||
|
||||
String warning = "This is an approximate calculated amount - it may vary slightly when the account is created";
|
||||
|
||||
HashMap<String, Object> result = new HashMap<>();
|
||||
result.put("maturityAmount", maturityAmount);
|
||||
result.put("totalPrincipal", totalPrincipal);
|
||||
result.put("totalInterest", totalInterest);
|
||||
result.put("daysInYear", 365); // Standard for this product as per image
|
||||
result.put("warning", warning);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public BigDecimal calculateInterestInternal(BigDecimal installmentAmount, BigDecimal annualInterestRate, Long tenureInMonths,
|
||||
Long interestCompoundingPeriodInMonths, LocalDate submittedOnDate) {
|
||||
MathContext mc = MoneyHelper.getMathContext();
|
||||
BigDecimal maturityAmount = BigDecimal.ZERO;
|
||||
int totalMonths = Math.toIntExact(tenureInMonths);
|
||||
|
||||
if (interestCompoundingPeriodInMonths == 0) {
|
||||
// Standard Banking Approach for Daily Compounding RD using Actual Calendar Days
|
||||
BigDecimal currentBalance = BigDecimal.ZERO;
|
||||
double dailyRate = annualInterestRate.doubleValue() / 36500.0;
|
||||
LocalDate date = submittedOnDate;
|
||||
|
||||
for (int m = 1; m <= totalMonths; m++) {
|
||||
// Add monthly installment at the start of the month
|
||||
currentBalance = currentBalance.add(installmentAmount);
|
||||
|
||||
// Get actual days in the current month
|
||||
int daysInMonth = date.lengthOfMonth();
|
||||
|
||||
// Calculate interest for the month using daily compounding
|
||||
double multiplier = Math.pow(1.0 + dailyRate, daysInMonth);
|
||||
currentBalance = currentBalance.multiply(BigDecimal.valueOf(multiplier), mc);
|
||||
|
||||
// Move to next month
|
||||
date = date.plusMonths(1);
|
||||
}
|
||||
maturityAmount = currentBalance;
|
||||
} else {
|
||||
// Periodic Compounding (Monthly, Quarterly, etc.)
|
||||
BigDecimal monthlyRate = annualInterestRate.divide(BigDecimal.valueOf(1200), mc);
|
||||
BigDecimal i = monthlyRate;
|
||||
int n = totalMonths;
|
||||
|
||||
// Standard RD formula for monthly compounding:
|
||||
// M = P * ((1+i)^n - 1) / i * (1+i)
|
||||
// For other periods, we can approximate or use a loop.
|
||||
// Using loop for consistency with various compounding periods.
|
||||
BigDecimal compoundingFrequencyMonths = BigDecimal.valueOf(interestCompoundingPeriodInMonths);
|
||||
BigDecimal ratePerCompounding = annualInterestRate.divide(BigDecimal.valueOf(100), mc)
|
||||
.multiply(compoundingFrequencyMonths).divide(BigDecimal.valueOf(12), mc);
|
||||
|
||||
for (int m = 1; m <= totalMonths; m++) {
|
||||
BigDecimal monthsRemaining = BigDecimal.valueOf(totalMonths - m + 1);
|
||||
BigDecimal numberOfCompoundings = monthsRemaining.divide(compoundingFrequencyMonths, mc);
|
||||
|
||||
BigDecimal installmentMaturity = installmentAmount.multiply(
|
||||
BigDecimal.ONE.add(ratePerCompounding).pow(numberOfCompoundings.intValue(), mc), mc);
|
||||
|
||||
// Add remaining partial compounding period interest (simple interest for the rest)
|
||||
BigDecimal remainingMonths = monthsRemaining.remainder(compoundingFrequencyMonths);
|
||||
if (remainingMonths.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal partialInterest = installmentMaturity.multiply(annualInterestRate).multiply(remainingMonths)
|
||||
.divide(BigDecimal.valueOf(1200), mc);
|
||||
installmentMaturity = installmentMaturity.add(partialInterest);
|
||||
}
|
||||
|
||||
maturityAmount = maturityAmount.add(installmentMaturity);
|
||||
}
|
||||
}
|
||||
|
||||
return maturityAmount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
}
|
||||
+8
@@ -798,6 +798,10 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
sqlBuilder.append("sa.linked_account_id as linkedAccountId, ");
|
||||
sqlBuilder.append("sa.linked_account_type as linkedAccountType, ");
|
||||
sqlBuilder.append("sa.linked_account_no as linkedAccountNo, ");
|
||||
sqlBuilder.append("sp.balance_swap_option as balanceSwapOption, ");
|
||||
sqlBuilder.append("sp.msme as msme, ");
|
||||
sqlBuilder.append("COALESCE(sa.nomination_facility, sp.nomination_facility) as nominationFacility, ");
|
||||
sqlBuilder.append("COALESCE(sa.auto_debit_facility, sp.auto_debit_facility) as autoDebitFacility, ");
|
||||
// Facility config fields
|
||||
sqlBuilder.append("safc.atm_facility as atmFacility, ");
|
||||
sqlBuilder.append("safc.atm_withdrawal_facility as atmWithdrawalFacility, ");
|
||||
@@ -1049,6 +1053,10 @@ public class SavingsAccountReadPlatformServiceImpl implements SavingsAccountRead
|
||||
accountData.setLinkedAccountId(JdbcSupport.getLong(rs, "linkedAccountId"));
|
||||
accountData.setLinkedAccountType(rs.getString("linkedAccountType"));
|
||||
accountData.setLinkedAccountNo(rs.getString("linkedAccountNo"));
|
||||
accountData.setBalanceSwapOption(rs.getObject("balanceSwapOption") != null ? rs.getBoolean("balanceSwapOption") : null);
|
||||
accountData.setMsme(rs.getObject("msme") != null ? rs.getBoolean("msme") : null);
|
||||
accountData.setNominationFacility(rs.getObject("nominationFacility") != null ? rs.getBoolean("nominationFacility") : null);
|
||||
accountData.setAutoDebitFacility(rs.getObject("autoDebitFacility") != null ? rs.getBoolean("autoDebitFacility") : null);
|
||||
|
||||
// Nominee as parsed map
|
||||
final String nomineeDataStr = rs.getString("nomineeData");
|
||||
|
||||
+28
-2
@@ -96,6 +96,7 @@ import org.apache.fineract.portfolio.note.domain.Note;
|
||||
import org.apache.fineract.portfolio.note.domain.NoteRepository;
|
||||
import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
|
||||
import org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
|
||||
import org.apache.fineract.portfolio.savings.SavingsAccountTransactionType;
|
||||
import org.apache.fineract.portfolio.savings.SavingsApiConstants;
|
||||
import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues;
|
||||
@@ -165,6 +166,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
private final GSIMRepositoy gsimRepository;
|
||||
private final SavingsAccountInterestPostingService savingsAccountInterestPostingService;
|
||||
private final ErrorHandler errorHandler;
|
||||
private final FacilityChargeAutoApplyService facilityChargeAutoApplyService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -199,6 +201,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
|
||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, false);
|
||||
|
||||
// Validate minimum opening balance
|
||||
BigDecimal openingBalanceForValidation = BigDecimal.ZERO;
|
||||
if (command.parameterExists("openingBalance")) {
|
||||
final BigDecimal ob = command.bigDecimalValueOfParameterNamed("openingBalance");
|
||||
if (ob != null) openingBalanceForValidation = ob;
|
||||
}
|
||||
final BigDecimal minRequiredOpening = account.getMinRequiredOpeningBalance();
|
||||
if (minRequiredOpening != null && minRequiredOpening.compareTo(BigDecimal.ZERO) > 0
|
||||
&& openingBalanceForValidation.compareTo(minRequiredOpening) < 0) {
|
||||
final List<ApiParameterError> errors = new ArrayList<>();
|
||||
errors.add(ApiParameterError.parameterError(
|
||||
"openingBalance",
|
||||
"Opening balance (" + openingBalanceForValidation + ") is less than minimum required opening balance (" + minRequiredOpening + ")",
|
||||
"validation.msg.savingsaccount.openingBalance.less.than.min.required",
|
||||
openingBalanceForValidation, minRequiredOpening));
|
||||
throw new PlatformApiDataValidationException(errors);
|
||||
}
|
||||
|
||||
checkClientOrGroupActive(account);
|
||||
final Set<Long> existingTransactionIds = new HashSet<>();
|
||||
final Set<Long> existingReversedTransactionIds = new HashSet<>();
|
||||
@@ -214,6 +234,13 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
final Locale locale = command.extractLocale();
|
||||
final DateTimeFormatter fmt = DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
|
||||
processPostActiveActions(account, fmt, existingTransactionIds, existingReversedTransactionIds);
|
||||
// Auto-apply facility charges based on product facility config
|
||||
facilityChargeAutoApplyService.applyFacilityChargesOnActivation(account);
|
||||
this.savingAccountRepositoryWrapper.saveAndFlush(account);
|
||||
// Collect any newly added facility activation charges (after flush so amountOutstanding is set)
|
||||
// payCharge() internally adds transactions to account.transactions via handleChargeTransactions
|
||||
// Do NOT add to existingTransactionIds — postJournalEntries must include them for GL posting
|
||||
facilityChargeAutoApplyService.collectActivationCharges(account);
|
||||
this.savingAccountRepositoryWrapper.saveAndFlush(account);
|
||||
}
|
||||
|
||||
@@ -1391,8 +1418,7 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi
|
||||
public SavingsAccountData fallbackPostInterest(SavingsAccountData savingsAccountData, boolean postInterestAs, LocalDate transactionDate,
|
||||
boolean backdatedTxnsAllowedTill, Throwable t) {
|
||||
// NOTE: allow caller to catch the exceptions
|
||||
// NOTE: wrap throwable only if really necessary
|
||||
throw errorHandler.getMappable(t, null, null, "savings.postinterest");
|
||||
throw org.apache.fineract.infrastructure.core.exception.ErrorHandler.getMappable(t, null, null, "savings.postinterest");
|
||||
}
|
||||
|
||||
@Transactional
|
||||
|
||||
+41
-4
@@ -47,6 +47,7 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuild
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.EntityTables;
|
||||
import org.apache.fineract.infrastructure.dataqueries.data.StatusEnum;
|
||||
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
|
||||
@@ -62,6 +63,7 @@ import org.apache.fineract.portfolio.account.service.AccountNumberGenerator;
|
||||
import org.apache.fineract.portfolio.client.domain.Client;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
|
||||
import org.apache.fineract.portfolio.client.service.ClientNomineeWritePlatformService;
|
||||
import org.apache.fineract.portfolio.group.domain.Group;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepository;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
@@ -113,6 +115,7 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
private final GSIMRepositoy gsimRepository;
|
||||
private final GroupRepositoryWrapper groupRepositoryWrapper;
|
||||
private final GroupSavingsIndividualMonitoringWritePlatformService gsimWritePlatformService;
|
||||
private final ClientNomineeWritePlatformService clientNomineeWritePlatformService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -143,6 +146,18 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
final AppUser submittedBy = this.context.authenticatedUser();
|
||||
|
||||
final SavingsAccount account = this.savingAccountAssembler.assembleFrom(command, submittedBy);
|
||||
|
||||
if (account.getClient() != null && account.productId() != null) {
|
||||
long existingCount = this.savingAccountRepository.countActiveAccountsByClientAndProduct(
|
||||
account.getClient().getId(), account.productId());
|
||||
if (existingCount > 0) {
|
||||
throw new PlatformDataIntegrityException(
|
||||
"error.msg.savings.account.duplicate.product.for.client",
|
||||
"Client already has an active account with the same product.",
|
||||
"productId", account.productId());
|
||||
}
|
||||
}
|
||||
|
||||
this.savingAccountRepository.save(account);
|
||||
String accountNumber = "";
|
||||
GroupSavingsIndividualMonitoring gsimAccount = null;
|
||||
@@ -231,6 +246,16 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
}
|
||||
}
|
||||
// end of gsim
|
||||
// Save nominees if provided in payload (same transaction)
|
||||
final com.google.gson.JsonObject root = command.parsedJson().getAsJsonObject();
|
||||
if (root.has("nominees") && !root.get("nominees").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineesIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "SB", root.get("nominees"));
|
||||
} else if (root.has("nominee") && !root.get("nominee").isJsonNull()) {
|
||||
clientNomineeWritePlatformService.saveNomineeIfPresent(
|
||||
account.clientId(), account.getAccountNumber(), "SB", root.get("nominee"));
|
||||
}
|
||||
|
||||
final Long savingsId = account.getId();
|
||||
if (command.parameterExists(SavingsApiConstants.datatables)) {
|
||||
this.entityDatatableChecksWritePlatformService.saveDatatables(StatusEnum.CREATE.getValue(), EntityTables.SAVINGS.getName(),
|
||||
@@ -692,16 +717,16 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
String msg = "Unknown data integrity issue with savings account.";
|
||||
String param = null;
|
||||
Object[] msgArgs;
|
||||
Throwable checkEx = realCause == null ? dve : realCause;
|
||||
if (checkEx.getMessage().contains("sa_account_no_UNIQUE")) {
|
||||
final String causeMsg = getRootCauseMessage(realCause, dve);
|
||||
if (causeMsg.contains("sa_account_no_UNIQUE")) {
|
||||
final String accountNo = command.stringValueOfParameterNamed("accountNo");
|
||||
msgCode += ".duplicate.accountNo";
|
||||
msg = "Savings account with accountNo " + accountNo + " already exists";
|
||||
param = "accountNo";
|
||||
msgArgs = new Object[] { accountNo, dve };
|
||||
} else if (checkEx.getMessage().contains("sa_externalid_UNIQUE")) {
|
||||
} else if (causeMsg.contains("sa_external_id_UNIQUE") || causeMsg.contains("m_savings_account_external_id_key")) {
|
||||
final String externalId = command.stringValueOfParameterNamed("externalId");
|
||||
msgCode += ".duplicate.externalId";
|
||||
msgCode = "error.msg.savingsaccount.duplicate.externalId";
|
||||
msg = "Savings account with externalId " + externalId + " already exists";
|
||||
param = "externalId";
|
||||
msgArgs = new Object[] { externalId, dve };
|
||||
@@ -712,4 +737,16 @@ public class SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl impl
|
||||
log.error("Error occured.", dve);
|
||||
throw ErrorHandler.getMappable(dve, msgCode, msg, param, msgArgs);
|
||||
}
|
||||
|
||||
private String getRootCauseMessage(final Throwable realCause, final Exception dve) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
Throwable t = realCause != null ? realCause : dve;
|
||||
while (t != null) {
|
||||
if (t.getMessage() != null) {
|
||||
sb.append(t.getMessage()).append(' ');
|
||||
}
|
||||
t = t.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+22
-6
@@ -55,6 +55,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.charge.service.ChargeDropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.client.service.ClientNomineeWritePlatformService;
|
||||
import org.apache.fineract.portfolio.client.service.ClientReadPlatformService;
|
||||
import org.apache.fineract.portfolio.common.service.DropdownReadPlatformService;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepository;
|
||||
@@ -97,6 +98,7 @@ import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityConfig
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimit;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductFacilityLimitRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.SavingsProductRepository;
|
||||
import org.apache.fineract.portfolio.savings.service.FacilityChargeAutoApplyService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountInterestRateChartReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.DepositAccountOnHoldTransactionReadPlatformService;
|
||||
@@ -146,8 +148,12 @@ import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatform
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsProductWritePlatformServiceJpaRepositoryImpl;
|
||||
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateEffectiveRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.RdBaseInterestRateSlabHistoryRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateEffectiveRepository;
|
||||
import org.apache.fineract.portfolio.savings.domain.FdBaseInterestRateSlabHistoryRepository;
|
||||
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.RdBaseInterestRateReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformService;
|
||||
import org.apache.fineract.portfolio.savings.service.FdBaseInterestRateReadPlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPoster;
|
||||
import org.apache.fineract.portfolio.savings.service.SavingsSchedularInterestPosterTask;
|
||||
import org.apache.fineract.portfolio.savings.service.search.SavingsAccountTransactionSearchService;
|
||||
@@ -171,6 +177,14 @@ public class SavingsConfiguration {
|
||||
return new RdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(FdBaseInterestRateReadPlatformService.class)
|
||||
public FdBaseInterestRateReadPlatformService fdBaseInterestRateReadPlatformService(
|
||||
FdBaseInterestRateEffectiveRepository effectiveRepository,
|
||||
FdBaseInterestRateSlabHistoryRepository slabRepository) {
|
||||
return new FdBaseInterestRateReadPlatformServiceImpl(effectiveRepository, slabRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(SavingsAccountTransactionSearchService.class)
|
||||
public SavingsAccountTransactionSearchService savingsAccountTransactionSearchService(PlatformSecurityContext context,
|
||||
@@ -277,13 +291,14 @@ public class SavingsConfiguration {
|
||||
SavingsAccountChargeAssembler savingsAccountChargeAssembler, AccountAssociationsRepository accountAssociationsRepository,
|
||||
FromJsonHelper fromJsonHelper, CalendarInstanceRepository calendarInstanceRepository,
|
||||
ConfigurationDomainService configurationDomainService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
|
||||
BusinessEventNotifierService businessEventNotifierService) {
|
||||
BusinessEventNotifierService businessEventNotifierService, SavingsAccountAssembler savingsAccountAssembler,
|
||||
ClientNomineeWritePlatformService clientNomineeWritePlatformService) {
|
||||
return new DepositApplicationProcessWritePlatformServiceJpaRepositoryImpl(context, savingAccountRepository,
|
||||
fixedDepositAccountRepository, recurringDepositAccountRepository, depositAccountAssembler, depositAccountDataValidator,
|
||||
accountNumberGenerator, clientRepository, groupRepository, savingsProductRepository, noteRepository, staffRepository,
|
||||
savingsAccountApplicationTransitionApiJsonValidator, savingsAccountChargeAssembler, accountAssociationsRepository,
|
||||
fromJsonHelper, calendarInstanceRepository, configurationDomainService, accountNumberFormatRepository,
|
||||
businessEventNotifierService);
|
||||
businessEventNotifierService, savingsAccountAssembler, clientNomineeWritePlatformService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -409,7 +424,7 @@ public class SavingsConfiguration {
|
||||
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, AppUserRepositoryWrapper appuserRepository,
|
||||
StandingInstructionRepository standingInstructionRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
GSIMRepositoy gsimRepository, SavingsAccountInterestPostingService savingsAccountInterestPostingService,
|
||||
ErrorHandler errorHandler) {
|
||||
ErrorHandler errorHandler, FacilityChargeAutoApplyService facilityChargeAutoApplyService) {
|
||||
return new SavingsAccountWritePlatformServiceJpaRepositoryImpl(context, fromApiJsonDeserializer, savingAccountRepositoryWrapper,
|
||||
staffRepository, savingsAccountTransactionRepository, savingAccountAssembler, savingsAccountTransactionDataValidator,
|
||||
savingsAccountChargeDataValidator, paymentDetailWritePlatformService, journalEntryWritePlatformService,
|
||||
@@ -417,7 +432,7 @@ public class SavingsConfiguration {
|
||||
chargeRepository, savingsAccountChargeRepository, holidayRepository, workingDaysRepository, configurationDomainService,
|
||||
depositAccountOnHoldTransactionRepository, entityDatatableChecksWritePlatformService, appuserRepository,
|
||||
standingInstructionRepository, businessEventNotifierService, gsimRepository, savingsAccountInterestPostingService,
|
||||
errorHandler);
|
||||
errorHandler, facilityChargeAutoApplyService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -432,13 +447,14 @@ public class SavingsConfiguration {
|
||||
SavingsAccountDomainService savingsAccountDomainService, SavingsAccountWritePlatformService savingsAccountWritePlatformService,
|
||||
AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GSIMRepositoy gsimRepository,
|
||||
GroupRepositoryWrapper groupRepositoryWrapper, GroupSavingsIndividualMonitoringWritePlatformService gsimWritePlatformService) {
|
||||
GroupRepositoryWrapper groupRepositoryWrapper, GroupSavingsIndividualMonitoringWritePlatformService gsimWritePlatformService,
|
||||
ClientNomineeWritePlatformService clientNomineeWritePlatformService) {
|
||||
return new SavingsApplicationProcessWritePlatformServiceJpaRepositoryImpl(context, savingAccountRepository, savingAccountAssembler,
|
||||
savingsAccountDataValidator, accountNumberGenerator, clientRepository, groupRepository, savingsProductRepository,
|
||||
noteRepository, staffRepository, savingsAccountApplicationTransitionApiJsonValidator, savingsAccountChargeAssembler,
|
||||
commandProcessingService, savingsAccountDomainService, savingsAccountWritePlatformService, accountNumberFormatRepository,
|
||||
businessEventNotifierService, entityDatatableChecksWritePlatformService, gsimRepository, groupRepositoryWrapper,
|
||||
gsimWritePlatformService);
|
||||
gsimWritePlatformService, clientNomineeWritePlatformService);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+2
@@ -35,6 +35,7 @@ import org.apache.fineract.useradministration.dsc.data.DscLoginChallengeStartRes
|
||||
import org.apache.fineract.useradministration.dsc.data.DscLoginStatusResponse;
|
||||
import org.apache.fineract.useradministration.dsc.data.DscLoginVerifyRequest;
|
||||
import org.apache.fineract.useradministration.dsc.service.DscLoginChallengeService;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
@@ -54,6 +55,7 @@ import org.springframework.stereotype.Component;
|
||||
*/
|
||||
@Path("/v1/login/dsc")
|
||||
@Component
|
||||
@ConditionalOnProperty("fineract.security.2fa.enabled")
|
||||
@Tag(name = "Login DSC", description = "DSC-based two-factor authentication")
|
||||
@RequiredArgsConstructor
|
||||
public class LoginDscApiResource {
|
||||
|
||||
@@ -407,7 +407,7 @@ server.tomcat.mbeanregistry.enabled=${FINERACT_SERVER_TOMCAT_MBEANREGISTRY_ENABL
|
||||
spring.datasource.hikari.driverClassName=${FINERACT_HIKARI_DRIVER_SOURCE_CLASS_NAME:org.postgresql.Driver}
|
||||
spring.datasource.hikari.jdbcUrl=${FINERACT_HIKARI_JDBC_URL:jdbc:postgresql://localhost:5432/fineract_tenants}
|
||||
spring.datasource.hikari.username=${FINERACT_HIKARI_USERNAME:postgres}
|
||||
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:root}
|
||||
spring.datasource.hikari.password=${FINERACT_HIKARI_PASSWORD:313863}
|
||||
spring.datasource.hikari.minimumIdle=${FINERACT_HIKARI_MINIMUM_IDLE:3}
|
||||
spring.datasource.hikari.maximumPoolSize=${FINERACT_HIKARI_MAXIMUM_POOL_SIZE:10}
|
||||
spring.datasource.hikari.idleTimeout=${FINERACT_HIKARI_IDLE_TIMEOUT:60000}
|
||||
|
||||
@@ -289,6 +289,11 @@
|
||||
<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"/>
|
||||
<include file="parts/0278_add_is_mandatory_to_joint_holder.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0279_add_fd_type_and_base_interest_rate_tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0280_add_fixed_deposit_product_codes.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0284_remove_product_code_unique_constraints.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0281_update_fd_base_interest_rate_seed_data.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0278_add_soft_delete_to_family_members.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0279_add_soft_delete_columns_client_tables.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0280_add_deletion_audit_and_version_columns.xml" relativeToChangelogFile="true"/>
|
||||
@@ -308,5 +313,7 @@
|
||||
<include file="parts/0294_add_tfa_token_to_dsc_challenge.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0295_unique_email_mobile_on_m_appuser.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0296_unique_nominee_per_account_type.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0297_seed_indian_states.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0297_fix_unique_nominee_per_account.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0298_unique_external_id_on_current_account.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0299_seed_indian_states.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?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">
|
||||
|
||||
<changeSet id="0278_add_is_mandatory_to_joint_holder" author="system">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<columnExists tableName="m_current_account_joint_holder" columnName="is_mandatory"/>
|
||||
</not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_current_account_joint_holder">
|
||||
<column name="is_mandatory" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0278b_add_is_mandatory_to_savings_joint_holder" author="system">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_savings_account_joint_holders"/>
|
||||
<not>
|
||||
<columnExists tableName="m_savings_account_joint_holders" columnName="is_mandatory"/>
|
||||
</not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_savings_account_joint_holders">
|
||||
<column name="is_mandatory" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+300
@@ -0,0 +1,300 @@
|
||||
<?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.1.xsd">
|
||||
|
||||
<!-- 1. Add fd_type column to m_savings_product -->
|
||||
<changeSet id="0279a_add_fd_type_to_savings_product" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_savings_product" columnName="fd_type"/></not>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_savings_product">
|
||||
<column name="fd_type" type="VARCHAR(20)" defaultValue="SINGLE">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
|
||||
<!-- 2. Create FD base interest rate effective (version) table -->
|
||||
<changeSet id="0279b_create_fd_base_interest_rate_effective" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_fd_base_interest_rate_effective"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_fd_base_interest_rate_effective">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="fd_type" type="VARCHAR(20)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="version_label" type="VARCHAR(100)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="effective_from_date" type="DATE">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="effective_to_date" type="DATE"/>
|
||||
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="remarks" type="VARCHAR(500)"/>
|
||||
<column name="created_by_id" type="BIGINT"/>
|
||||
<column name="created_date" type="DATE"/>
|
||||
</createTable>
|
||||
</changeSet>
|
||||
|
||||
<!-- 3. Create FD base interest rate slab history table -->
|
||||
<changeSet id="0279c_create_fd_base_interest_rate_slab_history" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><tableExists tableName="m_fd_base_interest_rate_slab_history"/></not>
|
||||
</preConditions>
|
||||
<createTable tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="id" type="BIGINT" autoIncrement="true">
|
||||
<constraints primaryKey="true" nullable="false"/>
|
||||
</column>
|
||||
<column name="effective_id" type="BIGINT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="sr_no" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="description" type="VARCHAR(200)"/>
|
||||
<!-- period_type_enum: 0=DAYS, 1=WEEKS, 2=MONTHS, 3=YEARS -->
|
||||
<column name="period_type_enum" type="INT" defaultValueNumeric="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="from_period" type="INT">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="to_period" type="INT"/>
|
||||
<column name="annual_interest_rate" type="DECIMAL(19,6)">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<!-- senior_citizen_rate: additional rate for senior citizens -->
|
||||
<column name="senior_citizen_rate" type="DECIMAL(19,6)" defaultValueNumeric="0"/>
|
||||
<column name="created_date" type="DATE"/>
|
||||
</createTable>
|
||||
<addForeignKeyConstraint
|
||||
baseTableName="m_fd_base_interest_rate_slab_history"
|
||||
baseColumnNames="effective_id"
|
||||
referencedTableName="m_fd_base_interest_rate_effective"
|
||||
referencedColumnNames="id"
|
||||
constraintName="fk_fd_base_rate_slab_effective"/>
|
||||
</changeSet>
|
||||
|
||||
<!-- 4. Seed initial FD base interest rate data -->
|
||||
<changeSet id="0279d_seed_fd_base_interest_rate_single" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- FD SINGLE: < 1 year, period in DAYS -->
|
||||
<insert tableName="m_fd_base_interest_rate_effective">
|
||||
<column name="fd_type" value="SINGLE"/>
|
||||
<column name="version_label" value="FD-SINGLE-V1"/>
|
||||
<column name="effective_from_date" value="2025-01-01"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="remarks" value="Initial FD Single (Short Term) rates"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0279e_seed_fd_single_slabs" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SINGLE'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- 1-29 days -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="1"/>
|
||||
<column name="description" value="1 to 29 Days"/>
|
||||
<column name="period_type_enum" valueNumeric="0"/>
|
||||
<column name="from_period" valueNumeric="1"/>
|
||||
<column name="to_period" valueNumeric="29"/>
|
||||
<column name="annual_interest_rate" valueNumeric="4.50"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="5.00"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 30-89 days -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="2"/>
|
||||
<column name="description" value="30 to 89 Days"/>
|
||||
<column name="period_type_enum" valueNumeric="0"/>
|
||||
<column name="from_period" valueNumeric="30"/>
|
||||
<column name="to_period" valueNumeric="89"/>
|
||||
<column name="annual_interest_rate" valueNumeric="5.00"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="5.50"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 90-179 days -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="3"/>
|
||||
<column name="description" value="90 to 179 Days"/>
|
||||
<column name="period_type_enum" valueNumeric="0"/>
|
||||
<column name="from_period" valueNumeric="90"/>
|
||||
<column name="to_period" valueNumeric="179"/>
|
||||
<column name="annual_interest_rate" valueNumeric="5.50"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="6.00"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 180+ days -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SINGLE' AND version_label='FD-SINGLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="4"/>
|
||||
<column name="description" value="180+ Days"/>
|
||||
<column name="period_type_enum" valueNumeric="0"/>
|
||||
<column name="from_period" valueNumeric="180"/>
|
||||
<column name="annual_interest_rate" valueNumeric="6.00"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="6.50"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0279f_seed_fd_base_interest_rate_double" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- FD DOUBLE: >= 1 year, period in YEARS -->
|
||||
<insert tableName="m_fd_base_interest_rate_effective">
|
||||
<column name="fd_type" value="DOUBLE"/>
|
||||
<column name="version_label" value="FD-DOUBLE-V1"/>
|
||||
<column name="effective_from_date" value="2025-01-01"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="remarks" value="Initial FD Double (Long Term) rates"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0279g_seed_fd_double_slabs" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'DOUBLE'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- 1-2 years -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="1"/>
|
||||
<column name="description" value="1 to 2 Years"/>
|
||||
<column name="period_type_enum" valueNumeric="3"/>
|
||||
<column name="from_period" valueNumeric="1"/>
|
||||
<column name="to_period" valueNumeric="2"/>
|
||||
<column name="annual_interest_rate" valueNumeric="6.50"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.00"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 3-5 years -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="2"/>
|
||||
<column name="description" value="3 to 5 Years"/>
|
||||
<column name="period_type_enum" valueNumeric="3"/>
|
||||
<column name="from_period" valueNumeric="3"/>
|
||||
<column name="to_period" valueNumeric="5"/>
|
||||
<column name="annual_interest_rate" valueNumeric="6.75"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.25"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 6-10 years -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="3"/>
|
||||
<column name="description" value="6 to 10 Years"/>
|
||||
<column name="period_type_enum" valueNumeric="3"/>
|
||||
<column name="from_period" valueNumeric="6"/>
|
||||
<column name="to_period" valueNumeric="10"/>
|
||||
<column name="annual_interest_rate" valueNumeric="7.00"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.50"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 11+ years -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='DOUBLE' AND version_label='FD-DOUBLE-V1')"/>
|
||||
<column name="sr_no" valueNumeric="4"/>
|
||||
<column name="description" value="11+ Years"/>
|
||||
<column name="period_type_enum" valueNumeric="3"/>
|
||||
<column name="from_period" valueNumeric="11"/>
|
||||
<column name="annual_interest_rate" valueNumeric="7.25"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.75"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0279h_seed_fd_base_interest_rate_special" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- FD SPECIAL: monthly interest payout, period in MONTHS -->
|
||||
<insert tableName="m_fd_base_interest_rate_effective">
|
||||
<column name="fd_type" value="SPECIAL"/>
|
||||
<column name="version_label" value="FD-SPECIAL-V1"/>
|
||||
<column name="effective_from_date" value="2025-01-01"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="remarks" value="Initial FD Special (Monthly Payout) rates"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0279i_seed_fd_special_slabs" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<sqlCheck expectedResult="0">SELECT COUNT(*) FROM m_fd_base_interest_rate_slab_history sh JOIN m_fd_base_interest_rate_effective e ON sh.effective_id = e.id WHERE e.fd_type = 'SPECIAL'</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- 1-12 months -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
|
||||
<column name="sr_no" valueNumeric="1"/>
|
||||
<column name="description" value="1 to 12 Months"/>
|
||||
<column name="period_type_enum" valueNumeric="2"/>
|
||||
<column name="from_period" valueNumeric="1"/>
|
||||
<column name="to_period" valueNumeric="12"/>
|
||||
<column name="annual_interest_rate" valueNumeric="6.00"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="6.50"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 13-24 months -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
|
||||
<column name="sr_no" valueNumeric="2"/>
|
||||
<column name="description" value="13 to 24 Months"/>
|
||||
<column name="period_type_enum" valueNumeric="2"/>
|
||||
<column name="from_period" valueNumeric="13"/>
|
||||
<column name="to_period" valueNumeric="24"/>
|
||||
<column name="annual_interest_rate" valueNumeric="6.50"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.00"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 25-60 months -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
|
||||
<column name="sr_no" valueNumeric="3"/>
|
||||
<column name="description" value="25 to 60 Months"/>
|
||||
<column name="period_type_enum" valueNumeric="2"/>
|
||||
<column name="from_period" valueNumeric="25"/>
|
||||
<column name="to_period" valueNumeric="60"/>
|
||||
<column name="annual_interest_rate" valueNumeric="7.00"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.50"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
<!-- 61+ months -->
|
||||
<insert tableName="m_fd_base_interest_rate_slab_history">
|
||||
<column name="effective_id" valueComputed="(SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type='SPECIAL' AND version_label='FD-SPECIAL-V1')"/>
|
||||
<column name="sr_no" valueNumeric="4"/>
|
||||
<column name="description" value="61+ Months"/>
|
||||
<column name="period_type_enum" valueNumeric="2"/>
|
||||
<column name="from_period" valueNumeric="61"/>
|
||||
<column name="annual_interest_rate" valueNumeric="7.25"/>
|
||||
<column name="senior_citizen_rate" valueNumeric="7.75"/>
|
||||
<column name="created_date" valueDate="2025-01-01"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+60
-15
@@ -34,7 +34,10 @@
|
||||
<!-- 1. Tables that already have is_active — add is_deleted only -->
|
||||
<changeSet id="0279a_is_deleted_client_address" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_address" columnName="is_deleted"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_address"/>
|
||||
<not><columnExists tableName="m_client_address" columnName="is_deleted"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_address">
|
||||
<column name="is_deleted" type="boolean" defaultValueBoolean="false">
|
||||
@@ -45,7 +48,10 @@
|
||||
|
||||
<changeSet id="0279b_is_deleted_client_nominee" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_nominee" columnName="is_deleted"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_nominee"/>
|
||||
<not><columnExists tableName="m_client_nominee" columnName="is_deleted"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_nominee">
|
||||
<column name="is_deleted" type="boolean" defaultValueBoolean="false">
|
||||
@@ -56,7 +62,10 @@
|
||||
|
||||
<changeSet id="0279c_is_deleted_client_charge" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_charge" columnName="is_deleted"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_charge"/>
|
||||
<not><columnExists tableName="m_client_charge" columnName="is_deleted"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_charge">
|
||||
<column name="is_deleted" type="boolean" defaultValueBoolean="false">
|
||||
@@ -68,7 +77,10 @@
|
||||
<!-- 2. Tables that have neither — add both -->
|
||||
<changeSet id="0279d_flags_client_non_person" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_non_person" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_non_person"/>
|
||||
<not><columnExists tableName="m_client_non_person" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_non_person">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -82,7 +94,10 @@
|
||||
|
||||
<changeSet id="0279e_flags_client_identifier" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_identifier" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_identifier"/>
|
||||
<not><columnExists tableName="m_client_identifier" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_identifier">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -96,7 +111,10 @@
|
||||
|
||||
<changeSet id="0279f_flags_client_kyc" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_kyc" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_kyc"/>
|
||||
<not><columnExists tableName="m_client_kyc" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_kyc">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -110,7 +128,10 @@
|
||||
|
||||
<changeSet id="0279g_flags_address" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_address" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_address"/>
|
||||
<not><columnExists tableName="m_address" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_address">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -124,7 +145,10 @@
|
||||
|
||||
<changeSet id="0279h_flags_client_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_priority_sector" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_priority_sector"/>
|
||||
<not><columnExists tableName="m_client_priority_sector" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_priority_sector">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -138,7 +162,10 @@
|
||||
|
||||
<changeSet id="0279i_flags_client_non_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_non_priority_sector" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_non_priority_sector"/>
|
||||
<not><columnExists tableName="m_client_non_priority_sector" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_non_priority_sector">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -152,7 +179,10 @@
|
||||
|
||||
<changeSet id="0279j_flags_client_msme" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_msme" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_msme"/>
|
||||
<not><columnExists tableName="m_client_msme" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_msme">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -166,7 +196,10 @@
|
||||
|
||||
<changeSet id="0279k_flags_client_mudra_borrower" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_mudra_borrower" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_mudra_borrower"/>
|
||||
<not><columnExists tableName="m_client_mudra_borrower" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_mudra_borrower">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -180,7 +213,10 @@
|
||||
|
||||
<changeSet id="0279l_flags_client_startup_india" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_startup_india" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_startup_india"/>
|
||||
<not><columnExists tableName="m_client_startup_india" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_startup_india">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -194,7 +230,10 @@
|
||||
|
||||
<changeSet id="0279m_flags_client_eou" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_eou" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_eou"/>
|
||||
<not><columnExists tableName="m_client_eou" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_eou">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -208,7 +247,10 @@
|
||||
|
||||
<changeSet id="0279n_flags_client_charge_paid_by" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_charge_paid_by" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_charge_paid_by"/>
|
||||
<not><columnExists tableName="m_client_charge_paid_by" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_charge_paid_by">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
@@ -222,7 +264,10 @@
|
||||
|
||||
<changeSet id="0279o_flags_client_collateral_management" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not><columnExists tableName="m_client_collateral_management" columnName="is_active"/></not>
|
||||
<and>
|
||||
<tableExists tableName="m_client_collateral_management"/>
|
||||
<not><columnExists tableName="m_client_collateral_management" columnName="is_active"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_collateral_management">
|
||||
<column name="is_active" type="boolean" defaultValueBoolean="true">
|
||||
|
||||
+96
-16
@@ -28,7 +28,12 @@
|
||||
-->
|
||||
|
||||
<changeSet id="0280a_deletion_audit_family_members" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_family_members" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_family_members"/>
|
||||
<not><columnExists tableName="m_family_members" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_family_members">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -37,7 +42,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280b_deletion_audit_client_address" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_address" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_address"/>
|
||||
<not><columnExists tableName="m_client_address" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_address">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -46,7 +56,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280c_deletion_audit_client_nominee" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_nominee" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_nominee"/>
|
||||
<not><columnExists tableName="m_client_nominee" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_nominee">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -55,7 +70,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280d_deletion_audit_client_charge" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_charge" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_charge"/>
|
||||
<not><columnExists tableName="m_client_charge" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_charge">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -64,7 +84,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280e_deletion_audit_client_non_person" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_non_person" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_non_person"/>
|
||||
<not><columnExists tableName="m_client_non_person" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_non_person">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -73,7 +98,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280f_deletion_audit_client_identifier" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_identifier" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_identifier"/>
|
||||
<not><columnExists tableName="m_client_identifier" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_identifier">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -82,7 +112,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280g_deletion_audit_client_kyc" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_kyc" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_kyc"/>
|
||||
<not><columnExists tableName="m_client_kyc" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_kyc">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -91,7 +126,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280h_deletion_audit_address" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_address" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_address"/>
|
||||
<not><columnExists tableName="m_address" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_address">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -100,7 +140,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280i_deletion_audit_client_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_priority_sector" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_priority_sector"/>
|
||||
<not><columnExists tableName="m_client_priority_sector" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_priority_sector">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -109,7 +154,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280j_deletion_audit_client_non_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_non_priority_sector" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_non_priority_sector"/>
|
||||
<not><columnExists tableName="m_client_non_priority_sector" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_non_priority_sector">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -118,7 +168,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280k_deletion_audit_client_msme" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_msme" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_msme"/>
|
||||
<not><columnExists tableName="m_client_msme" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_msme">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -127,7 +182,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280l_deletion_audit_client_mudra_borrower" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_mudra_borrower" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_mudra_borrower"/>
|
||||
<not><columnExists tableName="m_client_mudra_borrower" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_mudra_borrower">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -136,7 +196,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280m_deletion_audit_client_startup_india" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_startup_india" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_startup_india"/>
|
||||
<not><columnExists tableName="m_client_startup_india" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_startup_india">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -145,7 +210,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280n_deletion_audit_client_eou" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_eou" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_eou"/>
|
||||
<not><columnExists tableName="m_client_eou" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_eou">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -154,7 +224,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280o_deletion_audit_client_charge_paid_by" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_charge_paid_by" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_charge_paid_by"/>
|
||||
<not><columnExists tableName="m_client_charge_paid_by" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_charge_paid_by">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
@@ -163,7 +238,12 @@
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0280p_deletion_audit_client_collateral_management" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><not><columnExists tableName="m_client_collateral_management" columnName="deleted_at"/></not></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_collateral_management"/>
|
||||
<not><columnExists tableName="m_client_collateral_management" columnName="deleted_at"/></not>
|
||||
</and>
|
||||
</preConditions>
|
||||
<addColumn tableName="m_client_collateral_management">
|
||||
<column name="deleted_by" type="bigint"/>
|
||||
<column name="deleted_at" type="datetime"/>
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
<?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.1.xsd">
|
||||
|
||||
<!-- FixedDepositProductAccountType code and values -->
|
||||
<changeSet id="0280a_add_fd_product_account_type_code" author="system">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
INSERT INTO m_code (code_name, is_system_defined)
|
||||
SELECT 'FixedDepositProductAccountType', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductAccountType');
|
||||
</sql>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT c.id, v.code_value, v.order_position, true, false
|
||||
FROM m_code c,
|
||||
(VALUES
|
||||
('Fd Account', 1)
|
||||
) AS v(code_value, order_position)
|
||||
WHERE c.code_name = 'FixedDepositProductAccountType'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_code_value cv
|
||||
WHERE cv.code_id = c.id AND cv.code_value = v.code_value
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
<!-- FixedDepositProductCustomerSegment code and values -->
|
||||
<changeSet id="0280b_add_fd_product_customer_segment_code" author="system">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<sql>
|
||||
INSERT INTO m_code (code_name, is_system_defined)
|
||||
SELECT 'FixedDepositProductCustomerSegment', false
|
||||
WHERE NOT EXISTS (SELECT 1 FROM m_code WHERE code_name = 'FixedDepositProductCustomerSegment');
|
||||
</sql>
|
||||
<sql>
|
||||
INSERT INTO m_code_value (code_id, code_value, order_position, is_active, is_mandatory)
|
||||
SELECT c.id, v.code_value, v.order_position, true, false
|
||||
FROM m_code c,
|
||||
(VALUES
|
||||
('Individual', 1),
|
||||
('Non-Individual', 2)
|
||||
) AS v(code_value, order_position)
|
||||
WHERE c.code_name = 'FixedDepositProductCustomerSegment'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM m_code_value cv
|
||||
WHERE cv.code_id = c.id AND cv.code_value = v.code_value
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+54
-9
@@ -38,63 +38,108 @@
|
||||
<!-- =========================== Group A: rename =========================== -->
|
||||
|
||||
<changeSet id="0281a_rename_audit_client_nominee" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_nominee" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_nominee"/>
|
||||
<columnExists tableName="m_client_nominee" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_nominee" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_nominee" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_nominee" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281b_rename_audit_client_identifier" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_identifier" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_identifier"/>
|
||||
<columnExists tableName="m_client_identifier" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_identifier" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_identifier" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_identifier" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281c_rename_audit_client_kyc" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_kyc" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_kyc"/>
|
||||
<columnExists tableName="m_client_kyc" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_kyc" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_kyc" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_kyc" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281d_rename_audit_client_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_priority_sector" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_priority_sector"/>
|
||||
<columnExists tableName="m_client_priority_sector" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_priority_sector" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_priority_sector" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_priority_sector" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281e_rename_audit_client_non_priority_sector" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_non_priority_sector" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_non_priority_sector"/>
|
||||
<columnExists tableName="m_client_non_priority_sector" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_non_priority_sector" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_non_priority_sector" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_non_priority_sector" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281f_rename_audit_client_msme" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_msme" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_msme"/>
|
||||
<columnExists tableName="m_client_msme" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_msme" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_msme" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_msme" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281g_rename_audit_client_mudra_borrower" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_mudra_borrower" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_mudra_borrower"/>
|
||||
<columnExists tableName="m_client_mudra_borrower" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_mudra_borrower" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_mudra_borrower" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_mudra_borrower" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281h_rename_audit_client_startup_india" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_startup_india" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_startup_india"/>
|
||||
<columnExists tableName="m_client_startup_india" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_startup_india" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_startup_india" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_startup_india" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
</changeSet>
|
||||
|
||||
<changeSet id="0281i_rename_audit_client_eou" author="fineract">
|
||||
<preConditions onFail="MARK_RAN"><columnExists tableName="m_client_eou" columnName="last_modified_by"/></preConditions>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<and>
|
||||
<tableExists tableName="m_client_eou"/>
|
||||
<columnExists tableName="m_client_eou" columnName="last_modified_by"/>
|
||||
</and>
|
||||
</preConditions>
|
||||
<renameColumn tableName="m_client_eou" oldColumnName="last_modified_by" newColumnName="modified_by" columnDataType="bigint"/>
|
||||
<renameColumn tableName="m_client_eou" oldColumnName="created_on_utc" newColumnName="created_at" columnDataType="datetime"/>
|
||||
<renameColumn tableName="m_client_eou" oldColumnName="last_modified_on_utc" newColumnName="modified_on" columnDataType="datetime"/>
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
<?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.1.xsd">
|
||||
|
||||
<changeSet id="0281_update_fd_base_interest_rate_seed_data" author="fineract">
|
||||
<validCheckSum>ANY</validCheckSum>
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_fd_base_interest_rate_effective"/>
|
||||
</preConditions>
|
||||
|
||||
<sql>
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '1 to 29 Days',
|
||||
from_period = 1,
|
||||
to_period = 29
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
|
||||
AND sr_no = 1;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '30 to 89 Days',
|
||||
from_period = 30,
|
||||
to_period = 89
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
|
||||
AND sr_no = 2;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '90 to 179 Days',
|
||||
from_period = 90,
|
||||
to_period = 179
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
|
||||
AND sr_no = 3;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '180+ Days',
|
||||
from_period = 180,
|
||||
to_period = NULL
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SINGLE' AND version_label = 'FD-SINGLE-V1')
|
||||
AND sr_no = 4;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '1 to 2 Years',
|
||||
from_period = 1,
|
||||
to_period = 2
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
|
||||
AND sr_no = 1;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '3 to 5 Years',
|
||||
from_period = 3,
|
||||
to_period = 5
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
|
||||
AND sr_no = 2;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '6 to 10 Years',
|
||||
from_period = 6,
|
||||
to_period = 10
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
|
||||
AND sr_no = 3;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '11+ Years',
|
||||
from_period = 11,
|
||||
to_period = NULL
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'DOUBLE' AND version_label = 'FD-DOUBLE-V1')
|
||||
AND sr_no = 4;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '1 to 12 Months',
|
||||
from_period = 1,
|
||||
to_period = 12
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
|
||||
AND sr_no = 1;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '13 to 24 Months',
|
||||
from_period = 13,
|
||||
to_period = 24
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
|
||||
AND sr_no = 2;
|
||||
|
||||
UPDATE m_fd_base_interest_rate_slab_history
|
||||
SET description = '25 to 60 Months',
|
||||
from_period = 25,
|
||||
to_period = 60
|
||||
WHERE effective_id = (SELECT id FROM m_fd_base_interest_rate_effective WHERE fd_type = 'SPECIAL' AND version_label = 'FD-SPECIAL-V1')
|
||||
AND sr_no = 3;
|
||||
|
||||
INSERT INTO m_fd_base_interest_rate_slab_history
|
||||
(effective_id, sr_no, description, period_type_enum, from_period, to_period, annual_interest_rate, senior_citizen_rate, created_date)
|
||||
SELECT e.id, 4, '61+ Months', 2, 61, NULL, 7.250000, 7.750000, CURRENT_DATE
|
||||
FROM m_fd_base_interest_rate_effective e
|
||||
WHERE e.fd_type = 'SPECIAL' AND e.version_label = 'FD-SPECIAL-V1'
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM m_fd_base_interest_rate_slab_history sh
|
||||
WHERE sh.effective_id = e.id
|
||||
AND sh.sr_no = 4
|
||||
);
|
||||
</sql>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
<?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.1.xsd">
|
||||
|
||||
<changeSet id="0284_remove_product_code_unique_constraints" author="system">
|
||||
<!-- For Savings/RD/FD products -->
|
||||
<dropUniqueConstraint tableName="m_savings_product" constraintName="m_savings_product_product_code_key"/>
|
||||
|
||||
<!-- For Current products -->
|
||||
<dropUniqueConstraint tableName="m_current_product" constraintName="cp_unq_product_code"/>
|
||||
|
||||
<!-- Set existing empty or blank product_codes to NULL to avoid future issues -->
|
||||
<update tableName="m_savings_product">
|
||||
<column name="product_code" valueComputed="NULL"/>
|
||||
<where>product_code = '' OR product_code IS NULL</where>
|
||||
</update>
|
||||
|
||||
<update tableName="m_current_product">
|
||||
<column name="product_code" valueComputed="NULL"/>
|
||||
<where>product_code = '' OR product_code IS NULL</where>
|
||||
</update>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+20
-26
@@ -1,39 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Enforce uniqueness on m_appuser.email and m_appuser.mobile_no.
|
||||
|
||||
Username is already unique (constraint name 'username_org' from the upstream schema).
|
||||
Email is NOT NULL in the entity; mobile_no IS nullable. Both PostgreSQL and MySQL allow
|
||||
multiple NULLs in a UNIQUE constraint, so a plain UNIQUE works for mobile_no — multiple
|
||||
users with no mobile remain valid.
|
||||
|
||||
Note: if your existing data already contains duplicate emails or mobiles, this migration
|
||||
WILL fail. Dedupe first:
|
||||
SELECT LOWER(email), COUNT(*) FROM m_appuser GROUP BY LOWER(email) HAVING COUNT(*) > 1;
|
||||
SELECT mobile_no, COUNT(*) FROM m_appuser WHERE mobile_no IS NOT NULL GROUP BY mobile_no HAVING COUNT(*) > 1;
|
||||
-->
|
||||
|
||||
<changeSet id="0295_add_unique_email_to_m_appuser" author="fineract">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<not>
|
||||
<indexExists tableName="m_appuser" indexName="uq_m_appuser_email"/>
|
||||
</not>
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT LOWER(email)
|
||||
FROM m_appuser
|
||||
WHERE email IS NOT NULL
|
||||
GROUP BY LOWER(email)
|
||||
HAVING COUNT(*) > 1
|
||||
) t
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<addUniqueConstraint tableName="m_appuser" columnNames="email"
|
||||
constraintName="uq_m_appuser_email"/>
|
||||
@@ -44,8 +29,17 @@
|
||||
<not>
|
||||
<indexExists tableName="m_appuser" indexName="uq_m_appuser_mobile_no"/>
|
||||
</not>
|
||||
<sqlCheck expectedResult="0">
|
||||
SELECT COUNT(*)
|
||||
FROM (
|
||||
SELECT mobile_no
|
||||
FROM m_appuser
|
||||
WHERE mobile_no IS NOT NULL
|
||||
GROUP BY mobile_no
|
||||
HAVING COUNT(*) > 1
|
||||
) t
|
||||
</sqlCheck>
|
||||
</preConditions>
|
||||
<!-- Drop the plain non-unique index added in 0283 first — the unique constraint replaces it. -->
|
||||
<dropIndex tableName="m_appuser" indexName="idx_m_appuser_mobile_no"/>
|
||||
<addUniqueConstraint tableName="m_appuser" columnNames="mobile_no"
|
||||
constraintName="uq_m_appuser_mobile_no"/>
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
|
||||
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
|
||||
<!--
|
||||
Fix: previous constraint uq_client_nominee_per_account_type was on
|
||||
(client_id, nominee_client_id, account_type) — this incorrectly blocked the
|
||||
same nominee from being added to multiple accounts of the same type
|
||||
(e.g. two SB accounts). The correct uniqueness is per individual account:
|
||||
(client_id, nominee_client_id, account_no, account_type).
|
||||
-->
|
||||
<changeSet id="0297_fix_unique_nominee_per_account" author="fineract" dbms="postgresql">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_client_nominee"/>
|
||||
</preConditions>
|
||||
<sql>
|
||||
DROP INDEX IF EXISTS uq_client_nominee_per_account_type;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_client_nominee_per_account
|
||||
ON m_client_nominee (client_id, nominee_client_id, account_no, account_type)
|
||||
WHERE is_deleted = false;
|
||||
</sql>
|
||||
<rollback>
|
||||
<sql>
|
||||
DROP INDEX IF EXISTS uq_client_nominee_per_account;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_client_nominee_per_account_type
|
||||
ON m_client_nominee (client_id, nominee_client_id, account_type)
|
||||
WHERE is_deleted = false;
|
||||
</sql>
|
||||
</rollback>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?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.1.xsd">
|
||||
|
||||
<changeSet id="0298_unique_external_id_on_current_account" author="CBS">
|
||||
<preConditions onFail="MARK_RAN">
|
||||
<tableExists tableName="m_current_account"/>
|
||||
<not><indexExists tableName="m_current_account" indexName="ca_external_id_UNIQUE"/></not>
|
||||
<not><indexExists tableName="m_current_account" indexName="ca_unq_external_id"/></not>
|
||||
</preConditions>
|
||||
<addUniqueConstraint
|
||||
tableName="m_current_account"
|
||||
columnNames="external_id"
|
||||
constraintName="ca_external_id_UNIQUE"/>
|
||||
</changeSet>
|
||||
|
||||
</databaseChangeLog>
|
||||
+4
-1
@@ -45,6 +45,7 @@ import org.apache.fineract.portfolio.shareaccounts.domain.ShareAccount;
|
||||
import org.apache.fineract.portfolio.shareproducts.domain.ShareProduct;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class AccountNumberGeneratorTest {
|
||||
|
||||
@@ -54,6 +55,7 @@ public class AccountNumberGeneratorTest {
|
||||
private SavingsAccountRepository savingsRepo;
|
||||
private GLAccountRepository glAccountRepo;
|
||||
private SavingsProductRepository savingsProductRepo;
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
private AccountNumberGenerator generator;
|
||||
|
||||
@@ -65,8 +67,9 @@ public class AccountNumberGeneratorTest {
|
||||
savingsRepo = mock(SavingsAccountRepository.class);
|
||||
glAccountRepo = mock(GLAccountRepository.class);
|
||||
savingsProductRepo = mock(SavingsProductRepository.class);
|
||||
jdbcTemplate = mock(JdbcTemplate.class);
|
||||
|
||||
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo);
|
||||
generator = new AccountNumberGenerator(configService, clientRepo, loanRepo, savingsRepo, glAccountRepo, savingsProductRepo, jdbcTemplate);
|
||||
|
||||
GlobalConfigurationPropertyData accountLengthConfig = mock(GlobalConfigurationPropertyData.class);
|
||||
when(accountLengthConfig.getValue()).thenReturn(Long.valueOf("9"));
|
||||
|
||||
Reference in New Issue
Block a user