This is an automated email from the ASF dual-hosted git repository.

victorromero pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new 8a61e30a45 FINERACT-2486: Fix guarantor endpoint returning 
savingsAccount.id=0 for group savings (#5482)
8a61e30a45 is described below

commit 8a61e30a4521c77a59bc55f598e14c128ffd183a
Author: Ralph Hopman <[email protected]>
AuthorDate: Sat Feb 14 23:16:06 2026 +0100

    FINERACT-2486: Fix guarantor endpoint returning savingsAccount.id=0 for 
group savings (#5482)
---
 .../service/GuarantorReadPlatformServiceImpl.java  |   4 +-
 .../integrationtests/guarantor/GuarantorTest.java  | 147 +++++++++++++++++++++
 2 files changed, 149 insertions(+), 2 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
index 14828eabff..17f9d69958 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java
@@ -130,9 +130,9 @@ public class GuarantorReadPlatformServiceImpl implements 
GuarantorReadPlatformSe
                 .append(" left JOIN m_code_value cv on g.client_reln_cv_id = 
cv.id")//
                 .append(" left JOIN m_guarantor_funding_details gfd on g.id = 
gfd.guarantor_id")//
                 .append(" left JOIN m_portfolio_account_associations aa on 
gfd.account_associations_id = aa.id and aa.is_active = true and 
aa.association_type_enum = ?")//
-                .append(" left JOIN m_savings_account sa on sa.id = 
aa.linked_savings_account_id ")//
                 .append(" left join m_guarantor_transaction gt on 
gt.guarantor_fund_detail_id = gfd.id") //
-                .append(" left join m_deposit_account_on_hold_transaction oht 
on oht.id = gt.deposit_on_hold_transaction_id");
+                .append(" left join m_deposit_account_on_hold_transaction oht 
on oht.id = gt.deposit_on_hold_transaction_id")//
+                .append(" left JOIN m_savings_account sa on sa.id = 
COALESCE(aa.linked_savings_account_id, oht.savings_account_id)");
 
         public String schema() {
             return this.sqlBuilder.toString();
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTest.java
index 4d76c7709f..b10a095607 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTest.java
@@ -39,6 +39,7 @@ import java.util.Locale;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
 import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.GroupHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import 
org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
 import 
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
@@ -46,6 +47,8 @@ import 
org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
 import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
 import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
 import 
org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
+import 
org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
+import 
org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -735,4 +738,148 @@ public class GuarantorTest {
         return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
     }
 
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    @Test
+    public void testGuarantorWithGroupSavingsAccount() {
+        // Create a group
+        final Integer groupID = GroupHelper.createGroup(this.requestSpec, 
this.responseSpec, true);
+        Assertions.assertNotNull(groupID);
+        LOG.info("Created group with ID: {}", groupID);
+
+        // Create a client for the group
+        final Integer clientInGroupID = 
ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, clientInGroupID);
+        GroupHelper.associateClient(this.requestSpec, this.responseSpec, 
groupID.toString(), clientInGroupID.toString());
+        LOG.info("Created and associated client with ID: {}", clientInGroupID);
+
+        // Create a group savings product
+        final String minBalanceForInterestCalculation = null;
+        final String minRequiredBalance = null;
+        final String enforceMinRequiredBalance = "false";
+        final String minOpeningBalance = "5000.0";
+        final String loanProductJSON = new 
SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily()
+                
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
+                
.withMinBalanceForInterestCalculation(minBalanceForInterestCalculation).withMinRequiredBalance(minRequiredBalance)
+                
.withEnforceMinRequiredBalance(enforceMinRequiredBalance).withMinimumOpenningBalance(minOpeningBalance).build();
+        final Integer savingsProductID = 
SavingsProductHelper.createSavingsProduct(loanProductJSON, this.requestSpec, 
this.responseSpec);
+        Assertions.assertNotNull(savingsProductID);
+        LOG.info("Created savings product with ID: {}", savingsProductID);
+
+        // Create and activate a group savings account
+        final Integer groupSavingsId = 
this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, 
"GROUP");
+        Assertions.assertNotNull(groupSavingsId);
+        LOG.info("Applied for group savings account with ID: {}", 
groupSavingsId);
+
+        HashMap savingsStatusHashMap = 
this.savingsAccountHelper.approveSavings(groupSavingsId);
+        SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
+        LOG.info("Approved group savings account");
+
+        savingsStatusHashMap = 
this.savingsAccountHelper.activateSavings(groupSavingsId);
+        SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
+        LOG.info("Activated group savings account");
+
+        // Deposit money into the group savings account
+        Integer depositTransactionId = (Integer) 
this.savingsAccountHelper.depositToSavingsAccount(groupSavingsId, "5000",
+                SavingsAccountHelper.TRANSACTION_DATE, 
CommonConstants.RESPONSE_RESOURCE_ID);
+        Assertions.assertNotNull(depositTransactionId);
+        LOG.info("Deposited 5000 into group savings account");
+
+        // Create a client for the loan
+        final Integer loanClientID = 
ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, loanClientID);
+        LOG.info("Created loan client with ID: {}", loanClientID);
+
+        // Create a self savings account for the loan client (for self 
guarantee)
+        final Integer selfSavingsId = 
SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, 
loanClientID,
+                String.valueOf(5000.0));
+        Assertions.assertNotNull(selfSavingsId);
+        LOG.info("Created self savings account for loan client with ID: {}", 
selfSavingsId);
+
+        // Create another external client and savings account for additional 
external guarantee
+        final Integer externalClientID = 
ClientHelper.createClient(this.requestSpec, this.responseSpec);
+        ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, externalClientID);
+        final Integer externalSavingsId = 
SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, 
externalClientID,
+                String.valueOf(5000.0));
+        Assertions.assertNotNull(externalSavingsId);
+        LOG.info("Created external client with ID: {} and savings account with 
ID: {}", externalClientID, externalSavingsId);
+
+        // Create a loan product with hold funds
+        final Integer loanProductID = createLoanProductWithHoldFunds("0", "0", 
"0");
+        Assertions.assertNotNull(loanProductID);
+        LOG.info("Created loan product with ID: {}", loanProductID);
+
+        // Apply for a loan
+        final Integer loanID = applyForLoanApplication(loanClientID, 
loanProductID, SavingsAccountHelper.TRANSACTION_DATE);
+        Assertions.assertNotNull(loanID);
+        LOG.info("Applied for loan with ID: {}", loanID);
+
+        HashMap loanStatusHashMap = 
LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        // Create self guarantee from loan client's own savings
+        String guarantorJSON = new GuarantorTestBuilder()
+                
.existingCustomerWithGuaranteeAmount(String.valueOf(loanClientID), 
String.valueOf(selfSavingsId), "2000").build();
+        Integer selfGuarantorId = this.guarantorHelper.createGuarantor(loanID, 
guarantorJSON);
+        Assertions.assertNotNull(selfGuarantorId);
+        LOG.info("Created self guarantor with ID: {}", selfGuarantorId);
+
+        // Create a guarantor using the group savings account - THIS IS THE 
KEY TEST CASE
+        guarantorJSON = new GuarantorTestBuilder()
+                
.existingCustomerWithGuaranteeAmount(String.valueOf(clientInGroupID), 
String.valueOf(groupSavingsId), "2000").build();
+        final Integer groupSavingsGuarantorId = 
this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
+        Assertions.assertNotNull(groupSavingsGuarantorId);
+        LOG.info("Created guarantor with ID: {} using group savings account 
ID: {}", groupSavingsGuarantorId, groupSavingsId);
+
+        // Approve and disburse the loan
+        loanStatusHashMap = 
this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, 
loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LOG.info("Approved loan");
+
+        loanStatusHashMap = 
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(SavingsAccountHelper.TRANSACTION_DATE,
 loanID,
+                "10000");
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+        LOG.info("Disbursed loan");
+
+        // Retrieve the guarantor and verify the savings account ID is correct
+        List<HashMap> guarantors = 
this.guarantorHelper.getAllGuarantor(loanID);
+        Assertions.assertNotNull(guarantors);
+        Assertions.assertFalse(guarantors.isEmpty(), "Should have at least one 
guarantor");
+        LOG.info("Retrieved {} guarantor(s)", guarantors.size());
+
+        boolean foundGuarantorWithCorrectSavingsId = false;
+        for (HashMap guarantor : guarantors) {
+            if (guarantor.get("id").equals(groupSavingsGuarantorId)) {
+                LOG.info("Found guarantor with ID: {}", 
groupSavingsGuarantorId);
+
+                // Verify guarantorFundingDetails exists
+                List<HashMap> fundingDetails = (List<HashMap>) 
guarantor.get("guarantorFundingDetails");
+                Assertions.assertNotNull(fundingDetails, "Guarantor funding 
details should not be null");
+                Assertions.assertFalse(fundingDetails.isEmpty(), "Guarantor 
funding details should not be empty");
+                LOG.info("Found {} funding detail(s)", fundingDetails.size());
+
+                // Verify the savings account in funding details
+                for (HashMap fundingDetail : fundingDetails) {
+                    HashMap account = (HashMap) 
fundingDetail.get("savingsAccount");
+                    Assertions.assertNotNull(account, "Savings account in 
funding details should not be null");
+
+                    Integer savingsIdFromGuarantor = (Integer) 
account.get("id");
+                    LOG.info("Savings account ID from guarantor: {}, Expected: 
{}", savingsIdFromGuarantor, groupSavingsId);
+
+                    // This is the key assertion - verify that the savings 
account ID is not 0 and matches the group
+                    // savings ID
+                    Assertions.assertNotNull(savingsIdFromGuarantor, "Savings 
account ID should not be null");
+                    Assertions.assertNotEquals(Integer.valueOf(0), 
savingsIdFromGuarantor,
+                            "Savings account ID should not be 0 for group 
savings guarantor");
+                    Assertions.assertEquals(groupSavingsId, 
savingsIdFromGuarantor,
+                            "Savings account ID should match the group savings 
account ID");
+
+                    foundGuarantorWithCorrectSavingsId = true;
+                    LOG.info("VERIFIED: Group savings account ID {} is 
correctly returned in guarantor details", groupSavingsId);
+                }
+            }
+        }
+
+        Assertions.assertTrue(foundGuarantorWithCorrectSavingsId, "Should have 
found guarantor with correct group savings account ID");
+    }
+
 }

Reply via email to