FINERACT-2473: Add available balance to group accounts endpoint (#5468)
This commit is contained in:
+27
-9
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.accountdetails.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.organisation.monetary.data.CurrencyData;
|
||||
import org.apache.fineract.portfolio.savings.data.SavingsAccountApplicationTimelineData;
|
||||
@@ -29,6 +30,7 @@ import org.apache.fineract.portfolio.savings.data.SavingsAccountSubStatusEnumDat
|
||||
/**
|
||||
* Immutable data object for savings accounts.
|
||||
*/
|
||||
@Getter
|
||||
@SuppressWarnings("unused")
|
||||
public class SavingsAccountSummaryData {
|
||||
|
||||
@@ -40,7 +42,27 @@ public class SavingsAccountSummaryData {
|
||||
private final String shortProductName;
|
||||
private final SavingsAccountStatusEnumData status;
|
||||
private final CurrencyData currency;
|
||||
/**
|
||||
* The total balance of the savings account.
|
||||
*/
|
||||
private final BigDecimal accountBalance;
|
||||
/**
|
||||
* Funds held as collateral for loan guarantees. When a savings account is used as a guarantor for a loan, the
|
||||
* guarantee amount is held here and unavailable for withdrawal until the loan is repaid or the guarantee is
|
||||
* released. Sourced from the database column on_hold_funds_derived.
|
||||
*/
|
||||
private final BigDecimal onHoldFunds;
|
||||
/**
|
||||
* User-initiated holds explicitly placed on the account, including lien holds. These are manual holds placed
|
||||
* through hold/release transactions to restrict withdrawals for specific purposes. Sourced from the database column
|
||||
* total_savings_amount_on_hold.
|
||||
*/
|
||||
private final BigDecimal savingsAmountOnHold;
|
||||
/**
|
||||
* The actual available balance that can be withdrawn, accounting for all holds. Calculated as: accountBalance -
|
||||
* onHoldFunds (guarantor holds) - savingsAmountOnHold (user/lien holds)
|
||||
*/
|
||||
private final BigDecimal availableBalance;
|
||||
// differentiate Individual, JLG or Group account
|
||||
private final EnumOptionData accountType;
|
||||
private final SavingsAccountApplicationTimelineData timeline;
|
||||
@@ -52,7 +74,8 @@ public class SavingsAccountSummaryData {
|
||||
|
||||
public SavingsAccountSummaryData(final Long id, final String accountNo, final String externalId, final Long productId,
|
||||
final String productName, final String shortProductName, final SavingsAccountStatusEnumData status, final CurrencyData currency,
|
||||
final BigDecimal accountBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline,
|
||||
final BigDecimal accountBalance, final BigDecimal onHoldFunds, final BigDecimal savingsAmountOnHold,
|
||||
final BigDecimal availableBalance, final EnumOptionData accountType, final SavingsAccountApplicationTimelineData timeline,
|
||||
final EnumOptionData depositType, final SavingsAccountSubStatusEnumData subStatus, final LocalDate lastActiveTransactionDate) {
|
||||
this.id = id;
|
||||
this.accountNo = accountNo;
|
||||
@@ -63,18 +86,13 @@ public class SavingsAccountSummaryData {
|
||||
this.status = status;
|
||||
this.currency = currency;
|
||||
this.accountBalance = accountBalance;
|
||||
this.onHoldFunds = onHoldFunds;
|
||||
this.savingsAmountOnHold = savingsAmountOnHold;
|
||||
this.availableBalance = availableBalance;
|
||||
this.accountType = accountType;
|
||||
this.timeline = timeline;
|
||||
this.depositType = depositType;
|
||||
this.subStatus = subStatus;
|
||||
this.lastActiveTransactionDate = lastActiveTransactionDate;
|
||||
}
|
||||
|
||||
public String getAccountNo() {
|
||||
return accountNo;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-1
@@ -311,6 +311,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
accountsSummary.append("sa.id as id, sa.account_no as accountNo, sa.external_id as externalId, sa.status_enum as statusEnum, ");
|
||||
accountsSummary.append("sa.account_type_enum as accountType, ");
|
||||
accountsSummary.append("sa.account_balance_derived as accountBalance, ");
|
||||
accountsSummary.append("sa.on_hold_funds_derived as onHoldFunds, ");
|
||||
accountsSummary.append("sa.total_savings_amount_on_hold as onHoldAmount, ");
|
||||
|
||||
accountsSummary.append("sa.submittedon_date as submittedOnDate,");
|
||||
accountsSummary.append("sbu.username as submittedByUsername,");
|
||||
@@ -377,6 +379,18 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
final String shortProductName = rs.getString("shortProductName");
|
||||
final Integer statusId = JdbcSupport.getInteger(rs, "statusEnum");
|
||||
final BigDecimal accountBalance = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "accountBalance");
|
||||
final BigDecimal onHoldFunds = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldFunds");
|
||||
final BigDecimal onHoldAmount = JdbcSupport.getBigDecimalDefaultToNullIfZero(rs, "onHoldAmount");
|
||||
|
||||
BigDecimal availableBalance = accountBalance;
|
||||
if (availableBalance != null && onHoldFunds != null) {
|
||||
availableBalance = availableBalance.subtract(onHoldFunds);
|
||||
}
|
||||
|
||||
if (availableBalance != null && onHoldAmount != null) {
|
||||
availableBalance = availableBalance.subtract(onHoldAmount);
|
||||
}
|
||||
|
||||
final SavingsAccountStatusEnumData status = SavingsEnumerations.status(statusId);
|
||||
final Integer accountType = JdbcSupport.getInteger(rs, "accountType");
|
||||
final EnumOptionData accountTypeData = AccountEnumerations.loanType(accountType);
|
||||
@@ -433,7 +447,8 @@ public class AccountDetailsReadPlatformServiceJpaRepositoryImpl implements Accou
|
||||
activatedByLastname, closedOnDate, closedByUsername, closedByFirstname, closedByLastname);
|
||||
|
||||
return new SavingsAccountSummaryData(id, accountNo, externalId, productId, productName, shortProductName, status, currency,
|
||||
accountBalance, accountTypeData, timeline, depositTypeData, subStatus, lastActiveTransactionDate);
|
||||
accountBalance, onHoldFunds, onHoldAmount, availableBalance, accountTypeData, timeline, depositTypeData, subStatus,
|
||||
lastActiveTransactionDate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+8
@@ -415,6 +415,14 @@ final class GroupsApiResourceSwagger {
|
||||
public String productName;
|
||||
public GetGroupsGroupIdAccountsSavingStatus status;
|
||||
public GetGroupsGroupIdAccountsSavingCurrency currency;
|
||||
@Schema(example = "5000.00")
|
||||
public java.math.BigDecimal accountBalance;
|
||||
@Schema(example = "300.00")
|
||||
public java.math.BigDecimal onHoldFunds;
|
||||
@Schema(example = "200.00")
|
||||
public java.math.BigDecimal savingsAmountOnHold;
|
||||
@Schema(example = "4500.00")
|
||||
public java.math.BigDecimal availableBalance;
|
||||
public GetGroupsGroupIdAccountsSavingAccountType accountType;
|
||||
}
|
||||
|
||||
|
||||
+260
@@ -26,6 +26,7 @@ import io.restassured.http.ContentType;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
@@ -45,6 +46,7 @@ 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.LoanStatusChecker;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsAccountHelper;
|
||||
import org.apache.fineract.integrationtests.common.savings.SavingsProductHelper;
|
||||
@@ -72,6 +74,7 @@ public class GroupSavingsIntegrationTest {
|
||||
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 HOLD_AMOUNT = "300";
|
||||
public static final String ACCOUNT_TYPE_GROUP = "GROUP";
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
@@ -1021,4 +1024,261 @@ public class GroupSavingsIntegrationTest {
|
||||
"Should find at least one on-hold transaction with savingsClientName populated (group name)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupAccountAvailableBalance() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create a client
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID);
|
||||
Assertions.assertNotNull(clientID);
|
||||
|
||||
// Create a group and associate the client
|
||||
Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
|
||||
// Create a savings product
|
||||
final String minBalanceForInterestCalculation = null;
|
||||
final String minRequiredBalance = null;
|
||||
final String enforceMinRequiredBalance = "false";
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
|
||||
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
|
||||
Assertions.assertNotNull(savingsProductID);
|
||||
|
||||
// Apply for 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);
|
||||
|
||||
// Make a deposit to create a balance
|
||||
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
|
||||
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
Assertions.assertNotNull(depositTransactionId);
|
||||
|
||||
// Get the account summary to verify balance
|
||||
// Note: Account has minimum opening balance (1000) + deposit (2000) = 3000 total
|
||||
HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId);
|
||||
Float expectedBalance = Float.parseFloat(MINIMUM_OPENING_BALANCE) + Float.parseFloat(DEPOSIT_AMOUNT);
|
||||
assertEquals(expectedBalance, summary.get("accountBalance"), "Verifying Deposit Balance");
|
||||
|
||||
// Retrieve group accounts endpoint
|
||||
final String GROUP_ACCOUNTS_URL = "/fineract-provider/api/v1/groups/" + groupID + "/accounts?" + Utils.TENANT_IDENTIFIER;
|
||||
HashMap groupAccountsResponse = Utils.performServerGet(this.requestSpec, this.responseSpec, GROUP_ACCOUNTS_URL, "");
|
||||
Assertions.assertNotNull(groupAccountsResponse);
|
||||
|
||||
// Verify savingsAccounts array exists and has our account
|
||||
ArrayList<HashMap> savingsAccounts = (ArrayList<HashMap>) groupAccountsResponse.get("savingsAccounts");
|
||||
Assertions.assertNotNull(savingsAccounts, "savingsAccounts array should be present");
|
||||
Assertions.assertTrue(savingsAccounts.size() > 0, "savingsAccounts should contain at least one account");
|
||||
|
||||
// Find our savings account in the response
|
||||
HashMap account = null;
|
||||
for (HashMap acc : savingsAccounts) {
|
||||
if (acc.get("id").equals(savingsId)) {
|
||||
account = acc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assertions.assertNotNull(account, "Savings account should be in the response");
|
||||
|
||||
// Verify accountBalance and availableBalance fields are present
|
||||
Assertions.assertNotNull(account.get("accountBalance"), "accountBalance field should be present");
|
||||
Assertions.assertNotNull(account.get("availableBalance"), "availableBalance field should be present");
|
||||
|
||||
// Parse accountBalance
|
||||
BigDecimal accountBalance = new BigDecimal(account.get("accountBalance").toString());
|
||||
|
||||
// Parse hold fields (may be null if no holds exist)
|
||||
BigDecimal onHoldFunds = account.get("onHoldFunds") != null ? new BigDecimal(account.get("onHoldFunds").toString())
|
||||
: BigDecimal.ZERO;
|
||||
BigDecimal savingsAmountOnHold = account.get("savingsAmountOnHold") != null
|
||||
? new BigDecimal(account.get("savingsAmountOnHold").toString())
|
||||
: BigDecimal.ZERO;
|
||||
|
||||
// Parse availableBalance
|
||||
BigDecimal availableBalance = new BigDecimal(account.get("availableBalance").toString());
|
||||
|
||||
// Verify accountBalance matches expected total (minimum opening balance + deposit)
|
||||
assertEquals(0, expectedBalance.compareTo(Float.parseFloat(accountBalance.toString())),
|
||||
"accountBalance should equal minimum opening balance plus deposited amount");
|
||||
|
||||
// Since we haven't placed any holds, onHoldFunds and savingsAmountOnHold should be null or 0
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(onHoldFunds), "onHoldFunds should be 0 when no holds are placed");
|
||||
assertEquals(0, BigDecimal.ZERO.compareTo(savingsAmountOnHold), "savingsAmountOnHold should be 0 when no holds are placed");
|
||||
|
||||
// Verify calculation: availableBalance = accountBalance - onHoldFunds - savingsAmountOnHold
|
||||
BigDecimal expectedAvailableBalance = accountBalance.subtract(onHoldFunds).subtract(savingsAmountOnHold);
|
||||
assertEquals(0, expectedAvailableBalance.compareTo(availableBalance),
|
||||
"availableBalance should equal accountBalance - onHoldFunds - savingsAmountOnHold");
|
||||
|
||||
// Verify availableBalance equals accountBalance when there are no holds
|
||||
assertEquals(0, accountBalance.compareTo(availableBalance), "availableBalance should equal accountBalance when there are no holds");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupAccountWithHold() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create a group
|
||||
final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
|
||||
// Create a savings product
|
||||
final String minBalanceForInterestCalculation = null;
|
||||
final String minRequiredBalance = null;
|
||||
final String enforceMinRequiredBalance = "false";
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE,
|
||||
minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance);
|
||||
Assertions.assertNotNull(savingsProductID);
|
||||
|
||||
// Apply for 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);
|
||||
|
||||
// Make a deposit to create a balance
|
||||
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT,
|
||||
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
Assertions.assertNotNull(depositTransactionId);
|
||||
|
||||
// Place a hold on the account
|
||||
Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, HOLD_AMOUNT, false,
|
||||
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
Assertions.assertNotNull(holdTransactionId);
|
||||
|
||||
// Retrieve group accounts endpoint
|
||||
final String GROUP_ACCOUNTS_URL = "/fineract-provider/api/v1/groups/" + groupID + "/accounts?" + Utils.TENANT_IDENTIFIER;
|
||||
HashMap groupAccountsResponse = Utils.performServerGet(this.requestSpec, this.responseSpec, GROUP_ACCOUNTS_URL, "");
|
||||
Assertions.assertNotNull(groupAccountsResponse);
|
||||
|
||||
// Find our savings account in the response
|
||||
ArrayList<HashMap> savingsAccounts = (ArrayList<HashMap>) groupAccountsResponse.get("savingsAccounts");
|
||||
HashMap account = null;
|
||||
for (HashMap acc : savingsAccounts) {
|
||||
if (acc.get("id").equals(savingsId)) {
|
||||
account = acc;
|
||||
break;
|
||||
}
|
||||
}
|
||||
Assertions.assertNotNull(account, "Savings account should be in the response");
|
||||
|
||||
// Parse fields
|
||||
BigDecimal accountBalance = new BigDecimal(account.get("accountBalance").toString());
|
||||
BigDecimal onHoldFunds = account.get("onHoldFunds") != null ? new BigDecimal(account.get("onHoldFunds").toString())
|
||||
: BigDecimal.ZERO;
|
||||
BigDecimal savingsAmountOnHold = account.get("savingsAmountOnHold") != null
|
||||
? new BigDecimal(account.get("savingsAmountOnHold").toString())
|
||||
: BigDecimal.ZERO;
|
||||
BigDecimal availableBalance = new BigDecimal(account.get("availableBalance").toString());
|
||||
|
||||
// Verify the hold amount is reflected in savingsAmountOnHold
|
||||
assertEquals(0, new BigDecimal(HOLD_AMOUNT).compareTo(savingsAmountOnHold), "savingsAmountOnHold should equal the hold amount");
|
||||
|
||||
// Verify the calculation is correct: availableBalance = accountBalance - onHoldFunds - savingsAmountOnHold
|
||||
BigDecimal expectedAvailableBalance = accountBalance.subtract(onHoldFunds).subtract(savingsAmountOnHold);
|
||||
assertEquals(0, expectedAvailableBalance.compareTo(availableBalance),
|
||||
"availableBalance should equal accountBalance - onHoldFunds - savingsAmountOnHold");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that verifies group savings accounts can be used as guarantors when loan products have guarantee
|
||||
* requirements configured with zero minimum percentages.
|
||||
* <p>
|
||||
* Group accounts work with guarantees when minimum percentages are 0%, avoiding the self-guarantee validation logic
|
||||
* that expects guarantor.entityId to match loan.clientId (which fails for group accounts where client_id = null).
|
||||
* <p>
|
||||
* By using {@code withOnHoldFundDetails("0","0","0")}, we enable guarantee fund holds (isHoldGuaranteeFunds = true)
|
||||
* but set all minimum percentage requirements to 0%. This allows:
|
||||
* <ul>
|
||||
* <li>Validation to run (validateGuarantorBusinessRules() is called)</li>
|
||||
* <li>Group accounts to pass validation (no mandatory minimums to check)</li>
|
||||
* <li>Automatic holds to be placed on guarantor accounts upon loan disbursement</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Test
|
||||
public void testGroupAccountAsGuarantorWithGuaranteeHolds() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
this.loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
final GuarantorHelper guarantorHelper = new GuarantorHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create a borrower client
|
||||
final Integer borrowerClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, borrowerClientID);
|
||||
|
||||
// Create a group and associate the borrower with it
|
||||
Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
GroupHelper.associateClient(this.requestSpec, this.responseSpec, groupID.toString(), borrowerClientID.toString());
|
||||
|
||||
// Create a GROUP savings account (owned by group, not individual client)
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
final Integer guarantorSavingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID,
|
||||
ACCOUNT_TYPE_GROUP);
|
||||
Assertions.assertNotNull(guarantorSavingsId);
|
||||
|
||||
HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(guarantorSavingsId);
|
||||
SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap);
|
||||
|
||||
savingsStatusHashMap = this.savingsAccountHelper.activateSavings(guarantorSavingsId);
|
||||
SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap);
|
||||
|
||||
// Deposit funds into the group account
|
||||
final String depositAmount = "10000";
|
||||
Integer depositTransactionId = (Integer) this.savingsAccountHelper.depositToSavingsAccount(guarantorSavingsId, depositAmount,
|
||||
SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
Assertions.assertNotNull(depositTransactionId);
|
||||
|
||||
// Create loan product with guarantee requirements but zero minimum percentages
|
||||
// This allows group accounts to be used as guarantors while enabling automatic holds
|
||||
final String loanProductJSON = new LoanProductTestBuilder().withPrincipal("10000").withNumberOfRepayments("12")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0") // 0% mandatory, 0% self, 0% external
|
||||
.build(null);
|
||||
final Integer loanProductID = loanTransactionHelper.getLoanProductId(loanProductJSON);
|
||||
Assertions.assertNotNull(loanProductID);
|
||||
|
||||
// Create a basic loan for the borrower
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("10000").withLoanTermFrequency("12")
|
||||
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
|
||||
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("2").withAmortizationTypeAsEqualInstallments()
|
||||
.withInterestTypeAsDecliningBalance().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
|
||||
.withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE)
|
||||
.withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE_PLUS_ONE)
|
||||
.build(borrowerClientID.toString(), loanProductID.toString(), null);
|
||||
final Integer loanID = loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
Assertions.assertNotNull(loanID);
|
||||
|
||||
// Create a guarantor linking the group savings account to the loan
|
||||
final String guaranteeAmount = "5000";
|
||||
final String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingCustomerWithGuaranteeAmount(String.valueOf(borrowerClientID), String.valueOf(guarantorSavingsId), guaranteeAmount)
|
||||
.build();
|
||||
Integer guarantorId = guarantorHelper.createGuarantor(loanID, guarantorJSON);
|
||||
Assertions.assertNotNull(guarantorId, "Guarantor with group savings account created successfully");
|
||||
|
||||
// Approve the loan - THIS is when the hold is placed (not on disbursement!)
|
||||
HashMap loanStatusHashMap = loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID);
|
||||
LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
|
||||
|
||||
// Verify the group savings account has an automatic hold equal to the guarantee amount after approval
|
||||
HashMap savingsDetails = this.savingsAccountHelper.getSavingsDetails(guarantorSavingsId);
|
||||
Object onHoldFundsObj = savingsDetails.get("onHoldFunds");
|
||||
BigDecimal onHoldFunds = onHoldFundsObj != null ? new BigDecimal(onHoldFundsObj.toString()) : BigDecimal.ZERO;
|
||||
final BigDecimal expectedHoldAmount = new BigDecimal(guaranteeAmount);
|
||||
Assertions.assertEquals(expectedHoldAmount, onHoldFunds.setScale(0, RoundingMode.HALF_UP),
|
||||
"Group account should have automatic guarantor hold equal to guarantee amount after loan approval");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user