feat: FD/RD GL transaction prefixes + savingsAccountNo in journal entry API

- Use FD/RD transaction ID prefixes for Fixed/Recurring Deposit GL entries
  via ThreadLocal context set in Cash/AccrualBasedAccountingProcessorForSavings
- Add depositAccountType to SavingsDTO and pass from SavingsAccount bridge data
- Add savingsAccountNo field to JournalEntryData; populate via LEFT JOIN on
  m_savings_account (SB/FD/RD) and m_current_account (CA prefix) in read query
- Fix JournalEntryMapper MapStruct: ignore savingsAccountNo (SQL-populated)
- Fix JournalEntryRunningBalanceUpdateServiceImpl constructor call (27 params)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jaanu423
2026-05-20 18:26:15 +05:30
co-authored by Claude Sonnet 4.6
parent be26ba16c9
commit b25d5a30d2
9 changed files with 7492 additions and 7444 deletions
@@ -73,6 +73,7 @@ public interface JournalEntryMapper {
@Mapping(target = "transactionDetails", ignore = true) @Mapping(target = "transactionDetails", ignore = true)
@Mapping(target = "savingTransactionId", ignore = true) @Mapping(target = "savingTransactionId", ignore = true)
@Mapping(target = "externalAssetOwner", ignore = true) @Mapping(target = "externalAssetOwner", ignore = true)
@Mapping(target = "savingsAccountNo", ignore = true)
JournalEntryData map(JournalEntry journalEntry); JournalEntryData map(JournalEntry journalEntry);
@Named("entityType") @Named("entityType")
@@ -59,6 +59,8 @@ public class JournalEntryData {
@SuppressWarnings("unused") @SuppressWarnings("unused")
private Long entityId; private Long entityId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private String savingsAccountNo;
@SuppressWarnings("unused")
private Long createdByUserId; private Long createdByUserId;
@SuppressWarnings("unused") @SuppressWarnings("unused")
private OffsetDateTime createdDate; private OffsetDateTime createdDate;
@@ -193,7 +195,8 @@ public class JournalEntryData {
final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final OffsetDateTime submittedOnDate, final EnumOptionData entityType, final Long entityId, final Long createdByUserId, final OffsetDateTime submittedOnDate,
final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber, final String createdByUserName, final String comments, final Boolean reversed, final String referenceNumber,
final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed, final BigDecimal officeRunningBalance, final BigDecimal organizationRunningBalance, final Boolean runningBalanceComputed,
final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner) { final TransactionDetailData transactionDetailData, final CurrencyData currency, final String externalAssetOwner,
final String savingsAccountNo) {
this.id = id; this.id = id;
this.officeId = officeId; this.officeId = officeId;
this.officeName = officeName; this.officeName = officeName;
@@ -221,6 +224,7 @@ public class JournalEntryData {
this.transactionDetails = transactionDetailData; this.transactionDetails = transactionDetailData;
this.currency = currency; this.currency = currency;
this.externalAssetOwner = externalAssetOwner; this.externalAssetOwner = externalAssetOwner;
this.savingsAccountNo = savingsAccountNo;
} }
public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId, public static JournalEntryData importInstance(Long officeId, LocalDate transactionDate, String currencyCode, Long paymentTypeId,
@@ -266,7 +270,7 @@ public class JournalEntryData {
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification, return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glAccountCode, glAccountClassification,
transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate, transactionDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance, createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency, externalAssetOwner); runningBalanceComputed, transactionDetailData, currency, externalAssetOwner, null);
} }
public Integer getRowIndex() { public Integer getRowIndex() {
@@ -35,4 +35,5 @@ public class SavingsDTO {
private boolean cashBasedAccountingEnabled; private boolean cashBasedAccountingEnabled;
private boolean accrualBasedAccountingEnabled; private boolean accrualBasedAccountingEnabled;
private List<SavingsTransactionDTO> newSavingsTransactions; private List<SavingsTransactionDTO> newSavingsTransactions;
private int depositAccountType;
} }
@@ -83,10 +83,30 @@ public class AccountingProcessorHelper {
public static final String LOAN_TRANSACTION_IDENTIFIER = "L"; public static final String LOAN_TRANSACTION_IDENTIFIER = "L";
public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S"; public static final String SAVINGS_TRANSACTION_IDENTIFIER = "S";
public static final String FIXED_DEPOSIT_TRANSACTION_IDENTIFIER = "FD";
public static final String RECURRING_DEPOSIT_TRANSACTION_IDENTIFIER = "RD";
public static final String CLIENT_TRANSACTION_IDENTIFIER = "C"; public static final String CLIENT_TRANSACTION_IDENTIFIER = "C";
public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P"; public static final String PROVISIONING_TRANSACTION_IDENTIFIER = "P";
public static final String SHARE_TRANSACTION_IDENTIFIER = "SH"; public static final String SHARE_TRANSACTION_IDENTIFIER = "SH";
private static final ThreadLocal<Integer> DEPOSIT_ACCOUNT_TYPE_CONTEXT = ThreadLocal.withInitial(() -> 100);
public static void setDepositAccountTypeContext(int depositAccountType) {
DEPOSIT_ACCOUNT_TYPE_CONTEXT.set(depositAccountType);
}
public static void clearDepositAccountTypeContext() {
DEPOSIT_ACCOUNT_TYPE_CONTEXT.remove();
}
private String getSavingsTransactionIdentifier() {
return switch (DEPOSIT_ACCOUNT_TYPE_CONTEXT.get()) {
case 200 -> FIXED_DEPOSIT_TRANSACTION_IDENTIFIER;
case 300 -> RECURRING_DEPOSIT_TRANSACTION_IDENTIFIER;
default -> SAVINGS_TRANSACTION_IDENTIFIER;
};
}
private final JournalEntryRepository glJournalEntryRepository; private final JournalEntryRepository glJournalEntryRepository;
private final ProductToGLAccountMappingRepository accountMappingRepository; private final ProductToGLAccountMappingRepository accountMappingRepository;
private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository; private final FinancialActivityAccountRepositoryWrapper financialActivityAccountRepository;
@@ -203,6 +223,9 @@ public class AccountingProcessorHelper {
final Long loanProductId = (Long) accountingBridgeData.get("savingsProductId"); final Long loanProductId = (Long) accountingBridgeData.get("savingsProductId");
final Long officeId = (Long) accountingBridgeData.get("officeId"); final Long officeId = (Long) accountingBridgeData.get("officeId");
final String currencyCode = (String) accountingBridgeData.get("currencyCode"); final String currencyCode = (String) accountingBridgeData.get("currencyCode");
final int depositAccountType = accountingBridgeData.containsKey("depositAccountType")
? (Integer) accountingBridgeData.get("depositAccountType")
: 100;
final List<SavingsTransactionDTO> newSavingsTransactions = new ArrayList<>(); final List<SavingsTransactionDTO> newSavingsTransactions = new ArrayList<>();
boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer"); boolean isAccountTransfer = (Boolean) accountingBridgeData.get("isAccountTransfer");
@@ -264,7 +287,7 @@ public class AccountingProcessorHelper {
} }
return new SavingsDTO(loanId, loanProductId, officeId, currencyCode, cashBasedAccountingEnabled, accrualBasedAccountingEnabled, return new SavingsDTO(loanId, loanProductId, officeId, currencyCode, cashBasedAccountingEnabled, accrualBasedAccountingEnabled,
newSavingsTransactions); newSavingsTransactions, depositAccountType);
} }
public SharesDTO populateSharesDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled, public SharesDTO populateSharesDtoFromMap(final Map<String, Object> accountingBridgeData, final boolean cashBasedAccountingEnabled,
@@ -898,7 +921,7 @@ public class AccountingProcessorHelper {
String modifiedTransactionId = transactionId; String modifiedTransactionId = transactionId;
if (StringUtils.isNumeric(transactionId)) { if (StringUtils.isNumeric(transactionId)) {
savingsAccountTransactionId = Long.parseLong(transactionId); savingsAccountTransactionId = Long.parseLong(transactionId);
modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; modifiedTransactionId = getSavingsTransactionIdentifier() + transactionId;
} }
final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry,
transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null, transactionDate, JournalEntryType.CREDIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null,
@@ -964,7 +987,7 @@ public class AccountingProcessorHelper {
String modifiedTransactionId = transactionId; String modifiedTransactionId = transactionId;
if (StringUtils.isNumeric(transactionId)) { if (StringUtils.isNumeric(transactionId)) {
savingsAccountTransactionId = Long.parseLong(transactionId); savingsAccountTransactionId = Long.parseLong(transactionId);
modifiedTransactionId = SAVINGS_TRANSACTION_IDENTIFIER + transactionId; modifiedTransactionId = getSavingsTransactionIdentifier() + transactionId;
} }
final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry, final JournalEntry journalEntry = JournalEntry.createNew(office, null, account, currencyCode, modifiedTransactionId, manualEntry,
transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null, transactionDate, JournalEntryType.DEBIT, amount, null, PortfolioProductType.SAVING.getValue(), savingsId, null, null,
@@ -40,6 +40,8 @@ public class AccrualBasedAccountingProcessorForSavings implements AccountingProc
@Override @Override
public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) { public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) {
AccountingProcessorHelper.setDepositAccountTypeContext(savingsDTO.getDepositAccountType());
try {
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId()); final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId());
final Long savingsProductId = savingsDTO.getSavingsProductId(); final Long savingsProductId = savingsDTO.getSavingsProductId();
final Long savingsId = savingsDTO.getSavingsId(); final Long savingsId = savingsDTO.getSavingsId();
@@ -281,5 +283,8 @@ public class AccrualBasedAccountingProcessorForSavings implements AccountingProc
paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments); paymentTypeId, savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
} }
} }
} finally {
AccountingProcessorHelper.clearDepositAccountTypeContext();
}
} }
} }
@@ -39,6 +39,8 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
@Override @Override
public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) { public void createJournalEntriesForSavings(final SavingsDTO savingsDTO) {
AccountingProcessorHelper.setDepositAccountTypeContext(savingsDTO.getDepositAccountType());
try {
final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId()); final GLClosure latestGLClosure = this.helper.getLatestClosureByBranch(savingsDTO.getOfficeId());
final Long savingsProductId = savingsDTO.getSavingsProductId(); final Long savingsProductId = savingsDTO.getSavingsProductId();
final Long savingsId = savingsDTO.getSavingsId(); final Long savingsId = savingsDTO.getSavingsId();
@@ -251,5 +253,8 @@ public class CashBasedAccountingProcessorForSavings implements AccountingProcess
savingsId, transactionId, transactionDate, amount, isReversal, feePayments); savingsId, transactionId, transactionDate, amount, isReversal, feePayments);
} }
} }
} finally {
AccountingProcessorHelper.clearDepositAccountTypeContext();
}
} }
} }
@@ -104,7 +104,12 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
.append(" journalEntry.created_on_utc 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(" 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(" curr.display_symbol as currencyDisplaySymbol, curr.decimal_places as currencyDigits, curr.currency_multiplesof as inMultiplesOf, ")
.append(" eao.external_id as externalAssetOwner "); .append(" eao.external_id as externalAssetOwner, ")
.append(" CASE ")
.append(" WHEN journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' THEN ca.account_no ")
.append(" WHEN journalEntry.entity_type_enum = 2 THEN sa.account_no ")
.append(" ELSE NULL ")
.append(" END as savingsAccountNo ");
if (associationParametersData.isRunningBalanceRequired()) { if (associationParametersData.isRunningBalanceRequired()) {
sb.append(" ,journalEntry.is_running_balance_calculated as runningBalanceComputed, ") sb.append(" ,journalEntry.is_running_balance_calculated as runningBalanceComputed, ")
.append(" journalEntry.office_running_balance as officeRunningBalance, ") .append(" journalEntry.office_running_balance as officeRunningBalance, ")
@@ -124,7 +129,9 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
.append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.created_by ") .append(" left join m_appuser as creatingUser on creatingUser.id = journalEntry.created_by ")
.append(" join m_currency curr on curr.code = journalEntry.currency_code ") .append(" join m_currency curr on curr.code = journalEntry.currency_code ")
.append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ") .append(" left join m_external_asset_owner_journal_entry_mapping eajem on eajem.journal_entry_id = journalEntry.id ")
.append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id "); .append(" left join m_external_asset_owner eao on eao.id = eajem.owner_id ")
.append(" left join m_savings_account sa on sa.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id NOT LIKE 'CA%' ")
.append(" left join m_current_account ca on ca.id = journalEntry.entity_id AND journalEntry.entity_type_enum = 2 AND journalEntry.transaction_id LIKE 'CA%' ");
if (associationParametersData.isTransactionDetailsRequired()) { if (associationParametersData.isTransactionDetailsRequired()) {
sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ") sb.append(" left join m_loan_transaction as lt on journalEntry.loan_transaction_id = lt.id ")
.append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ") .append(" left join m_savings_account_transaction as st on journalEntry.savings_transaction_id = st.id ")
@@ -235,11 +242,12 @@ public class JournalEntryReadPlatformServiceImpl implements JournalEntryReadPlat
transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData); transactionDetailData = new TransactionDetailData(transaction, paymentDetailData, noteData, transactionTypeEnumData);
} }
final String externalAssetOwner = rs.getString("externalAssetOwner"); final String externalAssetOwner = rs.getString("externalAssetOwner");
final String savingsAccountNo = rs.getString("savingsAccountNo");
return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glCode, accountType, transactionDate, return new JournalEntryData(id, officeId, officeName, glAccountName, glAccountId, glCode, accountType, transactionDate,
entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate, entryType, amount, transactionId, manualEntry, entityType, entityId, createdByUserId, submittedOnDate,
createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance, createdByUserName, comments, reversed, referenceNumber, officeRunningBalance, organizationRunningBalance,
runningBalanceComputed, transactionDetailData, currency, externalAssetOwner); runningBalanceComputed, transactionDetailData, currency, externalAssetOwner, savingsAccountNo);
} }
} }
@@ -278,7 +278,7 @@ public class JournalEntryRunningBalanceUpdateServiceImpl implements JournalEntry
final EnumOptionData entryType = AccountingEnumerations.journalEntryType(entryTypeId); final EnumOptionData entryType = AccountingEnumerations.journalEntryType(entryTypeId);
return new JournalEntryData(id, officeId, null, null, glAccountId, null, accountType, null, entryType, amount, null, null, null, return new JournalEntryData(id, officeId, null, null, glAccountId, null, accountType, null, entryType, amount, null, null, null,
null, null, null, null, null, null, null, null, null, null, null, null, null); null, null, null, null, null, null, null, null, null, null, null, null, null, null);
} }
} }
@@ -1894,6 +1894,7 @@ public class SavingsAccount extends AbstractAuditableWithUTCDateTimeCustom<Long>
accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnSavingsProduct()); accountingBridgeData.put("cashBasedAccountingEnabled", isCashBasedAccountingEnabledOnSavingsProduct());
accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnSavingsProduct()); accountingBridgeData.put("accrualBasedAccountingEnabled", isAccrualBasedAccountingEnabledOnSavingsProduct());
accountingBridgeData.put("isAccountTransfer", isAccountTransfer); accountingBridgeData.put("isAccountTransfer", isAccountTransfer);
accountingBridgeData.put("depositAccountType", depositAccountType().getValue());
final List<Map<String, Object>> newSavingsTransactions = new ArrayList<>(); final List<Map<String, Object>> newSavingsTransactions = new ArrayList<>();