FINERACT-2482: On-hold transactions API returns empty results for group savings accounts
This commit is contained in:
committed by
Arnold Gálovics
parent
6269cbbb79
commit
9a69cc4826
+5
-3
@@ -94,16 +94,18 @@ public class DepositAccountOnHoldTransactionReadPlatformServiceImpl implements D
|
||||
sqlBuilder.append(" tr.id as transactionId, tr.transaction_type_enum as transactionType, ");
|
||||
sqlBuilder.append(" tr.transaction_date as transactionDate, tr.amount as transactionAmount,");
|
||||
sqlBuilder.append(" tr.is_reversed as reversed, sa.account_no as savingsAccNum, ");
|
||||
sqlBuilder.append("sc.display_name as savingsClientName, ml.id as loanid, sa.id as savingid, ");
|
||||
sqlBuilder.append(" ml.account_no as loanAccountNum, lc.display_name as loanClientName");
|
||||
sqlBuilder.append(" COALESCE(sc.display_name, sg.display_name) as savingsClientName, ml.id as loanid, sa.id as savingid, ");
|
||||
sqlBuilder.append(" ml.account_no as loanAccountNum, COALESCE(lc.display_name, lg.display_name) as loanClientName");
|
||||
sqlBuilder.append(" from m_savings_account sa ");
|
||||
sqlBuilder.append(" join m_deposit_account_on_hold_transaction tr on sa.id = tr.savings_account_id ");
|
||||
sqlBuilder.append(" join m_client sc on sc.id = sa.client_id");
|
||||
sqlBuilder.append(" left join m_client sc on sc.id = sa.client_id");
|
||||
sqlBuilder.append(" left join m_group sg on sg.id = sa.group_id");
|
||||
sqlBuilder.append(" left join m_guarantor_transaction gt on gt.deposit_on_hold_transaction_id = tr.id ");
|
||||
sqlBuilder.append(" left join m_guarantor_funding_details mgfd on mgfd.id=gt.guarantor_fund_detail_id");
|
||||
sqlBuilder.append(" left join m_portfolio_account_associations pa on pa.id=mgfd.account_associations_id");
|
||||
sqlBuilder.append(" left join m_loan ml on ml.id = pa.loan_account_id");
|
||||
sqlBuilder.append(" left join m_client lc on lc.id = ml.client_id");
|
||||
sqlBuilder.append(" left join m_group lg on lg.id = ml.group_id");
|
||||
|
||||
this.schemaSql = sqlBuilder.toString();
|
||||
}
|
||||
|
||||
+145
@@ -43,10 +43,15 @@ import org.apache.fineract.integrationtests.common.GroupHelper;
|
||||
import org.apache.fineract.integrationtests.common.PaymentTypeHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
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.apache.fineract.integrationtests.common.savings.SavingsTestLifecycleExtension;
|
||||
import org.apache.fineract.integrationtests.guarantor.GuarantorHelper;
|
||||
import org.apache.fineract.integrationtests.guarantor.GuarantorTestBuilder;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -65,12 +70,16 @@ public class GroupSavingsIntegrationTest {
|
||||
public static final String WITHDRAW_AMOUNT = "1000";
|
||||
public static final String WITHDRAW_AMOUNT_ADJUSTED = "500";
|
||||
public static final String MINIMUM_OPENING_BALANCE = "1000.0";
|
||||
public static final String PRINCIPAL = "5000";
|
||||
public static final String GUARANTEE_AMOUNT = "500";
|
||||
public static final String ACCOUNT_TYPE_GROUP = "GROUP";
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
private RequestSpecification requestSpec;
|
||||
private SavingsAccountHelper savingsAccountHelper;
|
||||
private PaymentTypeHelper paymentTypeHelper;
|
||||
private LoanTransactionHelper loanTransactionHelper;
|
||||
private GuarantorHelper guarantorHelper;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GroupSavingsIntegrationTest.class);
|
||||
|
||||
@BeforeEach
|
||||
@@ -81,6 +90,10 @@ public class GroupSavingsIntegrationTest {
|
||||
this.requestSpec.header("Fineract-Platform-TenantId", "default");
|
||||
this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build();
|
||||
this.paymentTypeHelper = new PaymentTypeHelper();
|
||||
// Use a default responseSpec for loan operations that doesn't enforce status code
|
||||
ResponseSpecification loanResponseSpec = new ResponseSpecBuilder().build();
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, loanResponseSpec);
|
||||
this.guarantorHelper = new GuarantorHelper(this.requestSpec, loanResponseSpec);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -876,4 +889,136 @@ public class GroupSavingsIntegrationTest {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verifies the /savingsaccounts/{savingsId}/onholdtransactions API endpoint works correctly for GROUP
|
||||
* savings accounts when used as guarantor collateral:
|
||||
* <ul>
|
||||
* <li>The endpoint returns hold transactions for group savings accounts</li>
|
||||
* <li>The savingsClientName field is populated with the group name (not null/blank)</li>
|
||||
* <li>Transaction details (amount, type, date) are correct</li>
|
||||
* <li>The response includes pagination information</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
public void testOnHoldTransactionsApiForGroupSavingsAccount() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create a client who will take out the loan
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
Assertions.assertNotNull(clientID);
|
||||
|
||||
// Create a group with a savings account that will act as collateral
|
||||
final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
|
||||
// Create a savings product
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
Assertions.assertNotNull(savingsProductID);
|
||||
|
||||
// Create and activate a group savings account
|
||||
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
|
||||
Assertions.assertNotNull(savingsId);
|
||||
|
||||
HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId);
|
||||
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
|
||||
|
||||
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId);
|
||||
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||
|
||||
// After activation, the account has MINIMUM_OPENING_BALANCE (1000) that can be used as collateral
|
||||
// No need for additional deposit
|
||||
|
||||
// Create a loan product with hold funds enabled
|
||||
// Note: Using 0,0,0 to bypass bug FINERACT-2476 where group accounts can't be guarantors
|
||||
// with non-zero guarantee requirements. This test focuses on the SQL query fix.
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final String loanProductJSON = loanProductBuilder.build(null);
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
Assertions.assertNotNull(loanProductID);
|
||||
|
||||
// Apply for a loan for the client
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(PRINCIPAL).withLoanTermFrequency("4")
|
||||
.withLoanTermFrequencyAsWeeks().withNumberOfRepayments("4").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks()
|
||||
.withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
|
||||
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE)
|
||||
.withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE)
|
||||
.build(clientID.toString(), loanProductID.toString(), null);
|
||||
final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
Assertions.assertNotNull(loanID);
|
||||
LOG.info("Created loan with ID: {}", loanID);
|
||||
|
||||
// Add the group savings account as guarantor collateral for the loan
|
||||
// Use GUARANTEE_AMOUNT (500) as guarantee amount (less than the MINIMUM_OPENING_BALANCE of 1000)
|
||||
String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingCustomerWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), GUARANTEE_AMOUNT).build();
|
||||
|
||||
LOG.info("Guarantor JSON: {}", guarantorJSON);
|
||||
LOG.info("Loan ID: {}, Group ID: {}, Savings ID: {}", loanID, groupID, savingsId);
|
||||
|
||||
Integer guarantorId = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
|
||||
LOG.info("Guarantor ID: {}", guarantorId);
|
||||
Assertions.assertNotNull(guarantorId, "Guarantor creation should return a valid ID");
|
||||
|
||||
// Approve the loan - this will create the hold transaction on the group savings account
|
||||
HashMap loanStatusHashMap = this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID);
|
||||
Assertions.assertNotNull(loanStatusHashMap);
|
||||
|
||||
// Call the on-hold transactions API endpoint
|
||||
final String ON_HOLD_TRANSACTIONS_URL = "/fineract-provider/api/v1/savingsaccounts/" + savingsId + "/onholdtransactions?"
|
||||
+ Utils.TENANT_IDENTIFIER;
|
||||
HashMap onHoldTransactionsResponse = Utils.performServerGet(this.requestSpec, this.responseSpec, ON_HOLD_TRANSACTIONS_URL, "");
|
||||
Assertions.assertNotNull(onHoldTransactionsResponse);
|
||||
|
||||
// Verify the response structure
|
||||
Assertions.assertTrue(onHoldTransactionsResponse.containsKey("totalFilteredRecords"),
|
||||
"Response should contain totalFilteredRecords");
|
||||
Assertions.assertTrue(onHoldTransactionsResponse.containsKey("pageItems"), "Response should contain pageItems");
|
||||
|
||||
// Verify we have at least one transaction (the guarantor hold we just created)
|
||||
Integer totalRecords = (Integer) onHoldTransactionsResponse.get("totalFilteredRecords");
|
||||
Assertions.assertTrue(totalRecords > 0, "Should have at least one on-hold transaction");
|
||||
|
||||
// Get the page items
|
||||
ArrayList<HashMap> pageItems = (ArrayList<HashMap>) onHoldTransactionsResponse.get("pageItems");
|
||||
Assertions.assertNotNull(pageItems, "pageItems should not be null");
|
||||
Assertions.assertFalse(pageItems.isEmpty(), "pageItems should not be empty");
|
||||
|
||||
LOG.info("Found {} on-hold transactions", pageItems.size());
|
||||
|
||||
// Verify that at least one transaction has the group name populated
|
||||
boolean foundTransactionWithGroupName = false;
|
||||
for (HashMap transaction : pageItems) {
|
||||
LOG.info("Transaction: {}", transaction);
|
||||
String savingsClientName = (String) transaction.get("savingsClientName");
|
||||
if (savingsClientName != null && !savingsClientName.isBlank()) {
|
||||
foundTransactionWithGroupName = true;
|
||||
|
||||
// Verify transaction details
|
||||
Assertions.assertNotNull(transaction.get("amount"), "Transaction amount should not be null");
|
||||
|
||||
// Verify savings account details are present
|
||||
String savingsAccNum = (String) transaction.get("savingsAccountNo");
|
||||
Assertions.assertNotNull(savingsAccNum, "savingsAccountNo should not be null");
|
||||
|
||||
// Verify savings ID matches
|
||||
Integer savingsIdFromTransaction = (Integer) transaction.get("savingsId");
|
||||
Assertions.assertEquals(savingsId, savingsIdFromTransaction, "savingsId should match");
|
||||
|
||||
// Verify transaction date is present
|
||||
Assertions.assertNotNull(transaction.get("transactionDate"), "transactionDate should not be null");
|
||||
|
||||
LOG.info("SUCCESS: Found on-hold transaction with group name '{}' for group savings account", savingsClientName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assertions.assertTrue(foundTransactionWithGroupName,
|
||||
"Should find at least one on-hold transaction with savingsClientName populated (group name)");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user