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
@@ -41,7 +41,10 @@ import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.portfolio.client.domain.Client;
@Entity
@Table(name = "m_current_account")
@Table(name = "m_current_account", uniqueConstraints = {
@jakarta.persistence.UniqueConstraint(columnNames = { "account_no" }, name = "ca_account_no_UNIQUE"),
@jakarta.persistence.UniqueConstraint(columnNames = { "external_id" }, name = "ca_unq_external_id")
})
@Getter
@Setter
public class CurrentAccount extends AbstractAuditableCustom {
@@ -37,4 +37,7 @@ public interface CurrentAccountRepository extends JpaRepository<CurrentAccount,
@Query("SELECT ca FROM CurrentAccount ca WHERE ca.client.id = :clientId AND ca.statusEnum != 600")
List<CurrentAccount> findActiveByClientId(@Param("clientId") Long clientId);
@Query("SELECT COUNT(ca) FROM CurrentAccount ca WHERE ca.client.id = :clientId AND ca.productId = :productId AND ca.statusEnum != 600")
long countActiveByClientAndProduct(@Param("clientId") Long clientId, @Param("productId") Long productId);
}