Ralph Hopman created FINERACT-2476:
--------------------------------------

             Summary: Enable Group Savings Accounts as Guarantors with 
Guarantee Percentage Requirements
                 Key: FINERACT-2476
                 URL: https://issues.apache.org/jira/browse/FINERACT-2476
             Project: Apache Fineract
          Issue Type: Improvement
          Components: Loan, Savings
            Reporter: Ralph Hopman


Group savings accounts cannot be used as guarantors when loan products have 
non-zero guarantee percentage requirements (mandatoryGuarantee, 
minimumGuaranteeFromGuarantor, minimumGuaranteeFromOwnFunds > 0), limiting 
valid business scenarios for group lending.
h2. Problem Statement

Group savings accounts are currently blocked from serving as guarantors when 
loan products enforce minimum guarantee percentages. This creates an artificial 
limitation that prevents valid business scenarios, particularly in group-based 
lending models where group savings serve as collective collateral.
h2. Current Behavior
 # *With zero guarantee requirements* 
({{{}withOnHoldFundDetails("0","0","0"){}}}):
 ** Group accounts work as guarantors
 ** Holds are placed correctly on loan approval
 # *With non-zero guarantee requirements* (e.g., 
{{{}withOnHoldFundDetails("20","10","10"){}}}):
 ** Group accounts fail validation during loan approval
 ** Error: Self-guarantee validation expects {{guarantor.entityId}} to match 
{{loan.clientId}}
 ** Group accounts have {{{}client_id = NULL{}}}, causing validation failure

h2. Expected Behavior

Group savings accounts should be usable as guarantors regardless of guarantee 
percentage requirements, as long as:
 * The guarantor account is active
 * Sufficient funds are available
 * The account is not in a restricted state
 * Standard guarantor business rules are met (excluding self-guarantee check 
for group accounts)

h1. Technical Analysis
h2. Root Cause

{*}File{*}: {{GuarantorDomainServiceImpl.java}}
{*}Method{*}: {{validateGuarantorBusinessRules(Loan loan)}}
{code:java}
if (loanProduct.isHoldGuaranteeFunds()) {
    // Validation logic runs here
    // Self-guarantee check assumes guarantor.entityId matches loan.clientId
    // For group accounts: client_id = NULL → validation fails
}
{code}
The validation logic was designed with individual client accounts in mind and 
does not account for group-owned accounts as valid guarantors.
h2. Database Schema Context

{*}Table{*}: {{m_savings_account}}
 * Individual client accounts: {{{}client_id = [client_id]{}}}, {{group_id = 
NULL}}
 * Group accounts: {{{}client_id = NULL{}}}, {{group_id = [group_id]}}

The self-guarantee validation checks if the savings account's {{client_id}} 
matches the loan borrower's {{{}client_id{}}}, which always fails for group 
accounts.
h2. Proposed Solution

Modify {{validateGuarantorBusinessRules()}} to:
 # Detect when a guarantor account is a group account ({{{}client_id = 
NULL{}}}, {{{}group_id != NULL{}}})
 # For group accounts, skip or adapt the self-guarantee validation
 # Optionally check if the borrower is a member of the guarantor group
 # Apply all other validation rules normally

h2. Functional Requirements
 * Group savings accounts can be added as guarantors for loans with non-zero 
guarantee requirements
 * Automatic holds are placed on group guarantor accounts equal to the 
guarantee amount
 * Holds are placed on loan approval (existing timing)
 * Validation enforces guarantee percentage requirements correctly
 * Individual client accounts continue to work exactly as before (no regression)
 * Self-guarantee validation still applies to individual client accounts

h2. Business Rules
 # Group account must be active
 # Group account must have sufficient available balance
 # Guarantee amount must meet minimum percentage requirements
 # Standard guarantor validation rules apply (excluding problematic 
self-guarantee check)

h2. Technical Requirements
 # No changes to database schema required
 # Backward compatible with existing guarantor records
 # API responses unchanged (maintaining contract)
 # Existing tests continue to pass

h1. Test Scenarios
h2. Test Case 1: Group Account as Guarantor with 20% Minimum Guarantee

{*}Setup{*}:
 * Loan product with {{withOnHoldFundDetails("20", "10", "10")}}
 * Loan amount: 10,000
 * Group savings account with balance: 10,000
 * Guarantee amount: 2,500 (25% of loan)

{*}Steps{*}:
 # Create loan with group guarantor
 # Approve loan

{*}Expected{*}:
 * Loan approval succeeds
 * Hold of 2,500 placed on group account
 * {{onHoldFunds = 2,500}}
 * {{availableBalance = 7,500}}

h2. Test Case 2: Insufficient Guarantee Percentage

{*}Setup{*}:
 * Loan product with {{withOnHoldFundDetails("20", "10", "10")}} (20% mandatory 
minimum)
 * Loan amount: 10,000
 * Group savings account with balance: 10,000
 * Guarantee amount: 1,000 (10% of loan)

{*}Expected{*}:
 * Validation error: Guarantee amount below minimum requirement

h2. Test Case 3: Mixed Guarantors

{*}Setup{*}:
 * Loan with multiple guarantors:
 ** Guarantor 1: Individual client account (3,000)
 ** Guarantor 2: Group account (2,000)
 * Loan amount: 10,000 with 50% guarantee requirement

{*}Expected{*}:
 * Total guarantee: 5,000 (50%) meets requirement
 * Holds placed on both accounts correctly

h1. Code References
h2. Files to Modify
 # *{{GuarantorDomainServiceImpl.java}}* (primary changes)
 ** Location: 
{{fineract-core/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/}}
 ** Method: {{validateGuarantorBusinessRules(Loan loan)}}
 ** Line: ~112 (where {{if (loanProduct.isHoldGuaranteeFunds())}} check occurs)
 # *Related Validation Logic*
 ** Self-guarantee checking logic
 ** Account ownership verification
 ** Minimum percentage validation

h2. Relevant Database Tables
 * {{m_savings_account}} (guarantor source accounts)
 * {{m_guarantor}} (guarantor relationships)
 * {{m_loan}} (borrower information)
 * {{m_portfolio_account_associations}} (account associations)

h2. Test Files to Update
 * {{GuarantorTest.java}} - Add group account scenarios
 * {{GroupSavingsIntegrationTest.java}} - Expand guarantor tests with non-zero 
percentages

h1. Example Validation Logic Modification
h2. Current Logic (Pseudocode)
{code:java}
if (loanProduct.isHoldGuaranteeFunds()) {
    for (Guarantor guarantor : loan.getGuarantors()) {
        // Validate account belongs to client
        if (!isAccountBelongsToClient(guarantor.savingsAccount, 
guarantor.entityId)) {
            throw new ValidationError("Account must belong to guarantor");
        }
        
        // Check self-guarantee
        if (guarantor.entityId.equals(loan.clientId)) {
            throw new ValidationError("Cannot self-guarantee");
        }
        
        // Other validations...
    }
}
{code}
h2. Proposed Logic (Pseudocode)
{code:java}
if (loanProduct.isHoldGuaranteeFunds()) {
    for (Guarantor guarantor : loan.getGuarantors()) {
        SavingsAccount savingsAccount = guarantor.getSavingsAccount();
        
        // Determine account type
        boolean isGroupAccount = (savingsAccount.getClientId() == null && 
                                  savingsAccount.getGroupId() != null);
        
        if (isGroupAccount) {
            // Group account validation path
            validateGroupGuarantorAccount(savingsAccount, loan);
        } else {
            // Individual account validation path (existing logic)
            if (!isAccountBelongsToClient(savingsAccount, guarantor.entityId)) {
                throw new ValidationError("Account must belong to guarantor");
            }
            
            // Check self-guarantee (only for individual accounts)
            if (guarantor.entityId.equals(loan.clientId)) {
                throw new ValidationError("Cannot self-guarantee");
            }
        }
        
        // Common validations for both account types
        validateAccountStatus(savingsAccount);
        validateSufficientFunds(savingsAccount, guarantor.amount);
        // Other validations...
    }
}
{code}
h1. Impact Assessment
h2. Business Impact
 * {*}High positive{*}: Enables valid group lending scenarios
 * {*}Low risk{*}: Only affects guarantor validation logic
 * {*}Backward compatible{*}: No impact on existing functionality

h2. Technical Impact
 * {*}Code changes{*}: Isolated to guarantor validation service
 * {*}Database{*}: No schema changes required
 * {*}API{*}: No breaking changes
 * {*}Performance{*}: Negligible (adds conditional check in validation)

h1. Additional Notes
h2. Workaround (Current)

Loan products can use {{.withOnHoldFundDetails("0","0","0")}} to enable group 
guarantors, but this:
 * Disables all guarantee percentage enforcement
 * Cannot enforce business rules requiring minimum guarantees
 * Proves the technical capability exists

h2. User Impact

Financial institutions using group-based lending models (common in 
microfinance) currently cannot:
 * Use group savings as collateral when guarantee percentages are required
 * Enforce minimum guarantee amounts with group accounts
 * Fully leverage group solidarity savings for loan security



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to