feat: multi-nominee support, duplicate validation, and FD/RD required fields

Account Creation Enhancements:
- Added multi-nominee support for SB, CA, FD, RD accounts (nominees[] array)
- Backend: saveNomineesIfPresent() validates total share <= 100% before saving
- Added 'nominees' to parameter whitelists (SavingsAccountConstant, DepositsApiConstants)

Duplicate Validation Fixes:
- SB/FD/RD: Fixed duplicate externalId error handler to walk full exception cause
  chain (EclipseLink wraps PSQLException deep) — now matches constraint
  'm_savings_account_external_id_key' and returns proper 400 error
- CA: Added try-catch around both saveAndFlush calls for duplicate externalId
  (constraint: ca_unq_external_id) with throwIfDuplicateExternalId() helper
- CA: Added duplicate product check — blocks creating same product twice for client
- SB/FD/RD: Added JpaSystemException handling via getAllCauseMessages() helper

Current Account (CA) Improvements:
- CurrentAccount entity: added @UniqueConstraint for external_id (ca_unq_external_id)
- CurrentAccountRepository: added countActiveByClientAndProduct() JPQL query
- Liquibase 0298: unique constraint on m_current_account.external_id

FD/RD Required Field Validation (Backend):
- DepositAccountDataValidator: depositPeriod now required for RD (was FD-only)
- DepositAccountDataValidator: mandatoryRecommendedDepositAmount now notNull+positive for RD
- DepositAccountDataValidator: customerSegment now required (notBlank) for both FD and RD

Liquibase Migrations:
- 0297: fix unique nominee per account constraint
- 0298: unique external_id on m_current_account
- 2014: add is_mandatory column to m_savings_account_joint_holders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Nitesh Yadav
2026-05-20 08:00:00 +05:30
co-authored by Claude Sonnet 4.6
parent 9d67afec3e
commit cc0ccbb461
18 changed files with 291 additions and 28 deletions
@@ -46,7 +46,7 @@ public class SavingsAccountConstant extends SavingsApiConstants {
guardianNameParamName, guardianCifNumberParamName, guardianRelationshipParamName,
guardianClientIdParamName, minorDateOfBirthParamName,
introducerIdParamName, introducerNameParamName, introducerCifNumberParamName,
nomineeParamName, linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
nomineeParamName, "nominees", linkedAccountIdParamName, linkedAccountTypeParamName, linkedAccountNoParamName,
accountFacilityConfigParamName, jointHoldersParamName,
balanceSwapOptionParamName, msmeParamName, autoDebitFacilityParamName));
@@ -56,6 +56,9 @@ public interface SavingsAccountRepository extends JpaRepository<SavingsAccount,
@Query("select case when (count (saving) > 0) then 'true' else 'false' end from SavingsAccount saving where saving.client.id = :clientId and saving.status in (100,200,300,303,304)")
boolean doNonClosedSavingAccountsExistForClient(@Param("clientId") Long clientId);
@Query("SELECT COUNT(sa) FROM SavingsAccount sa WHERE sa.client.id = :clientId AND sa.product.id = :productId AND sa.status IN (100, 200, 300, 303, 304)")
long countActiveAccountsByClientAndProduct(@Param("clientId") Long clientId, @Param("productId") Long productId);
@Query("select sa from SavingsAccount sa where sa.client.id is null and sa.group.id = :groupId")
List<SavingsAccount> findByGroupId(@Param("groupId") Long groupId);
@@ -181,4 +181,8 @@ public class SavingsAccountRepositoryWrapper {
public List<Long> findLoanIdsByStatusId(Integer status) {
return null;
}
public long countActiveAccountsByClientAndProduct(final Long clientId, final Long productId) {
return this.repository.countActiveAccountsByClientAndProduct(clientId, productId);
}
}
@@ -36,4 +36,5 @@
<include file="parts/2011_add_facility_charge_mapping.xml" relativeToChangelogFile="true" />
<include file="parts/2012_add_current_account_permissions.xml" relativeToChangelogFile="true" />
<include file="parts/2013_add_missing_current_account_columns.xml" relativeToChangelogFile="true" />
<include file="parts/2014_add_is_mandatory_to_joint_holders.xml" relativeToChangelogFile="true" />
</databaseChangeLog>
@@ -0,0 +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.
-->
<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="2014_add_is_mandatory_to_joint_holders" author="CBS">
<preConditions onFail="MARK_RAN">
<tableExists tableName="m_savings_account_joint_holders"/>
<not><columnExists tableName="m_savings_account_joint_holders" columnName="is_mandatory"/></not>
</preConditions>
<addColumn tableName="m_savings_account_joint_holders">
<column name="is_mandatory" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>