Skip to content

Commit 8a61e30

Browse files
authored
FINERACT-2486: Fix guarantor endpoint returning savingsAccount.id=0 for group savings (#5482)
1 parent 05662e6 commit 8a61e30

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/guarantor/service/GuarantorReadPlatformServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ private static final class GuarantorMapper implements RowMapper<GuarantorData> {
130130
.append(" left JOIN m_code_value cv on g.client_reln_cv_id = cv.id")//
131131
.append(" left JOIN m_guarantor_funding_details gfd on g.id = gfd.guarantor_id")//
132132
.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 = ?")//
133-
.append(" left JOIN m_savings_account sa on sa.id = aa.linked_savings_account_id ")//
134133
.append(" left join m_guarantor_transaction gt on gt.guarantor_fund_detail_id = gfd.id") //
135-
.append(" left join m_deposit_account_on_hold_transaction oht on oht.id = gt.deposit_on_hold_transaction_id");
134+
.append(" left join m_deposit_account_on_hold_transaction oht on oht.id = gt.deposit_on_hold_transaction_id")//
135+
.append(" left JOIN m_savings_account sa on sa.id = COALESCE(aa.linked_savings_account_id, oht.savings_account_id)");
136136

137137
public String schema() {
138138
return this.sqlBuilder.toString();

integration-tests/src/test/java/org/apache/fineract/integrationtests/guarantor/GuarantorTest.java

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,16 @@
3939
import org.apache.fineract.integrationtests.common.ClientHelper;
4040
import org.apache.fineract.integrationtests.common.CollateralManagementHelper;
4141
import org.apache.fineract.integrationtests.common.CommonConstants;
42+
import org.apache.fineract.integrationtests.common.GroupHelper;
4243
import org.apache.fineract.integrationtests.common.Utils;
4344
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
4445
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
4546
import org.apache.fineract.integrationtests.common.loans.LoanStatusChecker;
4647
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
4748
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
4849
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
50+
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
51+
import org.apache.fineract.integrationtests.common.savings.SavingsStatusChecker;
4952
import org.junit.jupiter.api.Assertions;
5053
import org.junit.jupiter.api.BeforeEach;
5154
import org.junit.jupiter.api.Test;
@@ -735,4 +738,148 @@ private Integer applyForLoanApplication(final Integer clientID, final Integer lo
735738
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
736739
}
737740

741+
@SuppressWarnings({ "rawtypes", "unchecked" })
742+
@Test
743+
public void testGuarantorWithGroupSavingsAccount() {
744+
// Create a group
745+
final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
746+
Assertions.assertNotNull(groupID);
747+
LOG.info("Created group with ID: {}", groupID);
748+
749+
// Create a client for the group
750+
final Integer clientInGroupID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
751+
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientInGroupID);
752+
GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), clientInGroupID.toString());
753+
LOG.info("Created and associated client with ID: {}", clientInGroupID);
754+
755+
// Create a group savings product
756+
final String minBalanceForInterestCalculation = null;
757+
final String minRequiredBalance = null;
758+
final String enforceMinRequiredBalance = "false";
759+
final String minOpeningBalance = "5000.0";
760+
final String loanProductJSON = new SavingsProductHelper().withInterestCompoundingPeriodTypeAsDaily()
761+
.withInterestPostingPeriodTypeAsMonthly().withInterestCalculationPeriodTypeAsDailyBalance()
762+
.withMinBalanceForInterestCalculation(minBalanceForInterestCalculation).withMinRequiredBalance(minRequiredBalance)
763+
.withEnforceMinRequiredBalance(enforceMinRequiredBalance).withMinimumOpenningBalance(minOpeningBalance).build();
764+
final Integer savingsProductID = SavingsProductHelper.createSavingsProduct(loanProductJSON, this.requestSpec, this.responseSpec);
765+
Assertions.assertNotNull(savingsProductID);
766+
LOG.info("Created savings product with ID: {}", savingsProductID);
767+
768+
// Create and activate a group savings account
769+
final Integer groupSavingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, "GROUP");
770+
Assertions.assertNotNull(groupSavingsId);
771+
LOG.info("Applied for group savings account with ID: {}", groupSavingsId);
772+
773+
HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(groupSavingsId);
774+
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
775+
LOG.info("Approved group savings account");
776+
777+
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(groupSavingsId);
778+
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
779+
LOG.info("Activated group savings account");
780+
781+
// Deposit money into the group savings account
782+
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(groupSavingsId, "5000",
783+
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
784+
Assertions.assertNotNull(depositTransactionId);
785+
LOG.info("Deposited 5000 into group savings account");
786+
787+
// Create a client for the loan
788+
final Integer loanClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
789+
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, loanClientID);
790+
LOG.info("Created loan client with ID: {}", loanClientID);
791+
792+
// Create a self savings account for the loan client (for self guarantee)
793+
final Integer selfSavingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, loanClientID,
794+
String.valueOf(5000.0));
795+
Assertions.assertNotNull(selfSavingsId);
796+
LOG.info("Created self savings account for loan client with ID: {}", selfSavingsId);
797+
798+
// Create another external client and savings account for additional external guarantee
799+
final Integer externalClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
800+
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, externalClientID);
801+
final Integer externalSavingsId = SavingsAccountHelper.openSavingsAccount(this.requestSpec, this.responseSpec, externalClientID,
802+
String.valueOf(5000.0));
803+
Assertions.assertNotNull(externalSavingsId);
804+
LOG.info("Created external client with ID: {} and savings account with ID: {}", externalClientID, externalSavingsId);
805+
806+
// Create a loan product with hold funds
807+
final Integer loanProductID = createLoanProductWithHoldFunds("0", "0", "0");
808+
Assertions.assertNotNull(loanProductID);
809+
LOG.info("Created loan product with ID: {}", loanProductID);
810+
811+
// Apply for a loan
812+
final Integer loanID = applyForLoanApplication(loanClientID, loanProductID, SavingsAccountHelper.TRANSACTION_DATE);
813+
Assertions.assertNotNull(loanID);
814+
LOG.info("Applied for loan with ID: {}", loanID);
815+
816+
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
817+
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
818+
819+
// Create self guarantee from loan client's own savings
820+
String guarantorJSON = new GuarantorTestBuilder()
821+
.existingCustomerWithGuaranteeAmount(String.valueOf(loanClientID), String.valueOf(selfSavingsId), "2000").build();
822+
Integer selfGuarantorId = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
823+
Assertions.assertNotNull(selfGuarantorId);
824+
LOG.info("Created self guarantor with ID: {}", selfGuarantorId);
825+
826+
// Create a guarantor using the group savings account - THIS IS THE KEY TEST CASE
827+
guarantorJSON = new GuarantorTestBuilder()
828+
.existingCustomerWithGuaranteeAmount(String.valueOf(clientInGroupID), String.valueOf(groupSavingsId), "2000").build();
829+
final Integer groupSavingsGuarantorId = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
830+
Assertions.assertNotNull(groupSavingsGuarantorId);
831+
LOG.info("Created guarantor with ID: {} using group savings account ID: {}", groupSavingsGuarantorId, groupSavingsId);
832+
833+
// Approve and disburse the loan
834+
loanStatusHashMap = this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID);
835+
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
836+
LOG.info("Approved loan");
837+
838+
loanStatusHashMap = this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount(SavingsAccountHelper.TRANSACTION_DATE, loanID,
839+
"10000");
840+
LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
841+
LOG.info("Disbursed loan");
842+
843+
// Retrieve the guarantor and verify the savings account ID is correct
844+
List<HashMap> guarantors = this.guarantorHelper.getAllGuarantor(loanID);
845+
Assertions.assertNotNull(guarantors);
846+
Assertions.assertFalse(guarantors.isEmpty(), "Should have at least one guarantor");
847+
LOG.info("Retrieved {} guarantor(s)", guarantors.size());
848+
849+
boolean foundGuarantorWithCorrectSavingsId = false;
850+
for (HashMap guarantor : guarantors) {
851+
if (guarantor.get("id").equals(groupSavingsGuarantorId)) {
852+
LOG.info("Found guarantor with ID: {}", groupSavingsGuarantorId);
853+
854+
// Verify guarantorFundingDetails exists
855+
List<HashMap> fundingDetails = (List<HashMap>) guarantor.get("guarantorFundingDetails");
856+
Assertions.assertNotNull(fundingDetails, "Guarantor funding details should not be null");
857+
Assertions.assertFalse(fundingDetails.isEmpty(), "Guarantor funding details should not be empty");
858+
LOG.info("Found {} funding detail(s)", fundingDetails.size());
859+
860+
// Verify the savings account in funding details
861+
for (HashMap fundingDetail : fundingDetails) {
862+
HashMap account = (HashMap) fundingDetail.get("savingsAccount");
863+
Assertions.assertNotNull(account, "Savings account in funding details should not be null");
864+
865+
Integer savingsIdFromGuarantor = (Integer) account.get("id");
866+
LOG.info("Savings account ID from guarantor: {}, Expected: {}", savingsIdFromGuarantor, groupSavingsId);
867+
868+
// This is the key assertion - verify that the savings account ID is not 0 and matches the group
869+
// savings ID
870+
Assertions.assertNotNull(savingsIdFromGuarantor, "Savings account ID should not be null");
871+
Assertions.assertNotEquals(Integer.valueOf(0), savingsIdFromGuarantor,
872+
"Savings account ID should not be 0 for group savings guarantor");
873+
Assertions.assertEquals(groupSavingsId, savingsIdFromGuarantor,
874+
"Savings account ID should match the group savings account ID");
875+
876+
foundGuarantorWithCorrectSavingsId = true;
877+
LOG.info("VERIFIED: Group savings account ID {} is correctly returned in guarantor details", groupSavingsId);
878+
}
879+
}
880+
}
881+
882+
Assertions.assertTrue(foundGuarantorWithCorrectSavingsId, "Should have found guarantor with correct group savings account ID");
883+
}
884+
738885
}

0 commit comments

Comments
 (0)