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)