FINERACT-2498: Add GROUP as a Guarantor Type to Support Group Savings Accounts as Loan Collateral
This commit is contained in:
+6
-1
@@ -25,7 +25,8 @@ public enum GuarantorType {
|
||||
|
||||
CUSTOMER(1, "guarantor.existing.customer"), //
|
||||
STAFF(2, "guarantor.staff"), //
|
||||
EXTERNAL(3, "guarantor.external"); //
|
||||
EXTERNAL(3, "guarantor.external"), //
|
||||
GROUP(4, "guarantor.existing.group"); //
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
@@ -90,4 +91,8 @@ public enum GuarantorType {
|
||||
return this.value.equals(GuarantorType.STAFF.getValue());
|
||||
}
|
||||
|
||||
public boolean isGroup() {
|
||||
return this.value.equals(GuarantorType.GROUP.getValue());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
@@ -141,6 +141,7 @@ public final class TemplatePopulateImportConstants {
|
||||
// Guarantor Types
|
||||
public static final String GUARANTOR_INTERNAL = "Internal";
|
||||
public static final String GUARANTOR_EXTERNAL = "External";
|
||||
public static final String GUARANTOR_GROUP = "Group";
|
||||
|
||||
// Loan Account/Loan repayment Client External Id
|
||||
public static final Boolean CONTAINS_CLIENT_EXTERNAL_ID = true;
|
||||
|
||||
+2
@@ -94,6 +94,8 @@ public class GuarantorImportHandler implements ImportHandler {
|
||||
guarantorTypeId = 1;
|
||||
} else if (guarantorType.equalsIgnoreCase(TemplatePopulateImportConstants.GUARANTOR_EXTERNAL)) {
|
||||
guarantorTypeId = 3;
|
||||
} else if (guarantorType.equalsIgnoreCase(TemplatePopulateImportConstants.GUARANTOR_GROUP)) {
|
||||
guarantorTypeId = 4;
|
||||
}
|
||||
}
|
||||
String clientName = ImportHandlerUtils.readAsString(GuarantorConstants.ENTITY_ID_COL, row);
|
||||
|
||||
+3
-2
@@ -209,8 +209,9 @@ public class GuarantorWorkbookPopulator extends AbstractWorkbookPopulator {
|
||||
"INDIRECT(CONCATENATE(\"Account_\",SUBSTITUTE(SUBSTITUTE(SUBSTITUTE($B1,\" \",\"_\"),\"(\",\"_\"),\")\",\"_\")))");
|
||||
DataValidationConstraint savingsaccountNumberConstraint = validationHelper.createFormulaListConstraint(
|
||||
"INDIRECT(CONCATENATE(\"SavingsAccount_\",SUBSTITUTE(SUBSTITUTE(SUBSTITUTE($G1,\" \",\"_\"),\"(\",\"_\"),\")\",\"_\")))");
|
||||
DataValidationConstraint guranterTypeConstraint = validationHelper.createExplicitListConstraint(
|
||||
new String[] { TemplatePopulateImportConstants.GUARANTOR_INTERNAL, TemplatePopulateImportConstants.GUARANTOR_EXTERNAL });
|
||||
DataValidationConstraint guranterTypeConstraint = validationHelper
|
||||
.createExplicitListConstraint(new String[] { TemplatePopulateImportConstants.GUARANTOR_INTERNAL,
|
||||
TemplatePopulateImportConstants.GUARANTOR_EXTERNAL, TemplatePopulateImportConstants.GUARANTOR_GROUP });
|
||||
DataValidationConstraint guarantorRelationshipConstraint = validationHelper.createFormulaListConstraint("GuarantorRelationship");
|
||||
DataValidationConstraint entityofficeNameConstraint = validationHelper.createFormulaListConstraint("Office");
|
||||
DataValidationConstraint entityclientNameConstraint = validationHelper
|
||||
|
||||
+4
@@ -207,4 +207,8 @@ public class GuarantorData implements IGuarantor {
|
||||
public boolean isStaffMember() {
|
||||
return GuarantorType.STAFF.getValue().equals(this.guarantorType.getId().intValue());
|
||||
}
|
||||
|
||||
public boolean isExistingGroup() {
|
||||
return GuarantorType.GROUP.getValue().equals(this.guarantorType.getId().intValue());
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -188,6 +188,10 @@ public class Guarantor extends AbstractPersistableCustom<Long> {
|
||||
return GuarantorType.STAFF.getValue().equals(this.gurantorType);
|
||||
}
|
||||
|
||||
public boolean isExistingGroup() {
|
||||
return GuarantorType.GROUP.getValue().equals(this.gurantorType);
|
||||
}
|
||||
|
||||
public boolean isExternalGuarantor() {
|
||||
return GuarantorType.EXTERNAL.getValue().equals(this.gurantorType);
|
||||
}
|
||||
|
||||
+1
-1
@@ -390,7 +390,7 @@ public class GuarantorDomainServiceImpl implements GuarantorDomainService {
|
||||
if (guarantor.isSelfGuarantee()) {
|
||||
selfGuarantorList.add(guarantorFundingDetails);
|
||||
selfGuarantee = selfGuarantee.add(guarantorFundingDetails.getAmountRemaining());
|
||||
} else if (guarantor.isExistingCustomer()) {
|
||||
} else if (guarantor.isExistingCustomer() || guarantor.isExistingGroup()) {
|
||||
externalGuarantorList.add(guarantorFundingDetails);
|
||||
guarantorGuarantee = guarantorGuarantee.add(guarantorFundingDetails.getAmountRemaining());
|
||||
}
|
||||
|
||||
+19
-3
@@ -38,6 +38,7 @@ import org.apache.fineract.portfolio.account.domain.AccountAssociationType;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountAssociations;
|
||||
import org.apache.fineract.portfolio.account.domain.AccountAssociationsRepository;
|
||||
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.guarantor.GuarantorConstants;
|
||||
@@ -70,6 +71,7 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
|
||||
|
||||
private final ClientRepositoryWrapper clientRepositoryWrapper;
|
||||
private final StaffRepositoryWrapper staffRepositoryWrapper;
|
||||
private final GroupRepositoryWrapper groupRepositoryWrapper;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final GuarantorRepository guarantorRepository;
|
||||
private final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer;
|
||||
@@ -81,11 +83,13 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
|
||||
@Autowired
|
||||
public GuarantorWritePlatformServiceJpaRepositoryIImpl(final LoanRepositoryWrapper loanRepositoryWrapper,
|
||||
final GuarantorRepository guarantorRepository, final ClientRepositoryWrapper clientRepositoryWrapper,
|
||||
final StaffRepositoryWrapper staffRepositoryWrapper, final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
final StaffRepositoryWrapper staffRepositoryWrapper, final GroupRepositoryWrapper groupRepositoryWrapper,
|
||||
final GuarantorCommandFromApiJsonDeserializer fromApiJsonDeserializer,
|
||||
final CodeValueRepositoryWrapper codeValueRepositoryWrapper, final SavingsAccountAssembler savingsAccountAssembler,
|
||||
final AccountAssociationsRepository accountAssociationsRepository, final GuarantorDomainService guarantorDomainService) {
|
||||
this.loanRepositoryWrapper = loanRepositoryWrapper;
|
||||
this.clientRepositoryWrapper = clientRepositoryWrapper;
|
||||
this.groupRepositoryWrapper = groupRepositoryWrapper;
|
||||
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
|
||||
this.guarantorRepository = guarantorRepository;
|
||||
this.staffRepositoryWrapper = staffRepositoryWrapper;
|
||||
@@ -148,6 +152,8 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
|
||||
String defaultUserMessage = null;
|
||||
if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) {
|
||||
defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName();
|
||||
} else if (guarantorTypeId.equals(GuarantorType.GROUP.getValue())) {
|
||||
defaultUserMessage = this.groupRepositoryWrapper.findOneWithNotFoundDetection(entityId).getName();
|
||||
} else {
|
||||
defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
|
||||
}
|
||||
@@ -225,12 +231,19 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
|
||||
final List<Guarantor> existGuarantorList = this.guarantorRepository.findByLoan(loan);
|
||||
final Integer guarantorTypeId = guarantorCommand.getGuarantorTypeId();
|
||||
final GuarantorType guarantorType = GuarantorType.fromInt(guarantorTypeId);
|
||||
if (guarantorType.isCustomer() || guarantorType.isStaff()) {
|
||||
if (guarantorType.isCustomer() || guarantorType.isStaff() || guarantorType.isGroup()) {
|
||||
final Long entityId = guarantorCommand.getEntityId();
|
||||
for (final Guarantor guarantor : existGuarantorList) {
|
||||
if (guarantor.getEntityId().equals(entityId) && guarantor.getGurantorType().equals(guarantorTypeId)
|
||||
&& !guarantorForUpdate.getId().equals(guarantor.getId())) {
|
||||
String defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
|
||||
String defaultUserMessage = null;
|
||||
if (guarantorTypeId.equals(GuarantorType.STAFF.getValue())) {
|
||||
defaultUserMessage = this.staffRepositoryWrapper.findOneWithNotFoundDetection(entityId).displayName();
|
||||
} else if (guarantorTypeId.equals(GuarantorType.GROUP.getValue())) {
|
||||
defaultUserMessage = this.groupRepositoryWrapper.findOneWithNotFoundDetection(entityId).getName();
|
||||
} else {
|
||||
defaultUserMessage = this.clientRepositoryWrapper.findOneWithNotFoundDetection(entityId).getDisplayName();
|
||||
}
|
||||
defaultUserMessage = defaultUserMessage + " is already exist as a guarantor for this loan";
|
||||
final String action = loan.client() != null ? "client.guarantor" : "group.guarantor";
|
||||
throw new DuplicateGuarantorException(action, "is.already.exist.same.loan", defaultUserMessage, entityId, loanId);
|
||||
@@ -336,6 +349,9 @@ public class GuarantorWritePlatformServiceJpaRepositoryIImpl implements Guaranto
|
||||
|
||||
} else if (guarantor.isExistingEmployee()) {
|
||||
this.staffRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
|
||||
} else if (guarantor.isExistingGroup()) {
|
||||
// check group exists
|
||||
this.groupRepositoryWrapper.findOneWithNotFoundDetection(guarantor.getEntityId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+317
-1
@@ -957,7 +957,7 @@ public class GroupSavingsIntegrationTest {
|
||||
// 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();
|
||||
.existingGroupWithGuaranteeAmount(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);
|
||||
@@ -1023,6 +1023,322 @@ public class GroupSavingsIntegrationTest {
|
||||
"Should find at least one on-hold transaction with savingsClientName populated (group name)");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that creating a group guarantor with an invalid group ID fails with appropriate error
|
||||
*/
|
||||
@Test
|
||||
public void testGroupGuarantorWithInvalidGroupId() {
|
||||
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);
|
||||
Assertions.assertNotNull(clientID);
|
||||
|
||||
// Create loan product with hold funds
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null));
|
||||
Assertions.assertNotNull(loanProductID);
|
||||
|
||||
// Apply for a loan
|
||||
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);
|
||||
|
||||
// Try to create guarantor with invalid group ID (9999999)
|
||||
final Integer invalidGroupId = 9999999;
|
||||
String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingGroupWithGuaranteeAmount(String.valueOf(invalidGroupId), "1", GUARANTEE_AMOUNT).build();
|
||||
|
||||
final ResponseSpecification errorResponse = new ResponseSpecBuilder().build();
|
||||
final RequestSpecification errorRequest = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
errorRequest.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
|
||||
ArrayList<HashMap> error = (ArrayList<HashMap>) this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON, errorRequest,
|
||||
errorResponse);
|
||||
// Verify we got an error response (status code may be 403 or 404 depending on environment)
|
||||
Assertions.assertNotNull(error, "Should return error for invalid group ID");
|
||||
|
||||
LOG.info("SUCCESS: Invalid group ID correctly rejected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that using a client ID with GROUP guarantor type fails with appropriate error
|
||||
*/
|
||||
@Test
|
||||
public void testGroupGuarantorWithClientIdButGroupType() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create TWO clients - one for loan, one to misuse as "group"
|
||||
final Integer loanClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(loanClientID);
|
||||
|
||||
final Integer otherClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(otherClientID);
|
||||
|
||||
// Create savings account for the other client
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
final Integer clientSavingsId = this.savingsAccountHelper.applyForSavingsApplication(otherClientID, savingsProductID, "INDIVIDUAL");
|
||||
this.savingsAccountHelper.approveSavings(clientSavingsId);
|
||||
this.savingsAccountHelper.activateSavings(clientSavingsId);
|
||||
|
||||
// Create loan product
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null));
|
||||
|
||||
// Create loan
|
||||
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(loanClientID.toString(), loanProductID.toString(), null);
|
||||
final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
|
||||
// Try to create guarantor with CLIENT ID but GROUP type (type mismatch)
|
||||
String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingGroupWithGuaranteeAmount(String.valueOf(otherClientID), String.valueOf(clientSavingsId), GUARANTEE_AMOUNT).build();
|
||||
|
||||
final ResponseSpecification errorResponse = new ResponseSpecBuilder().build();
|
||||
final RequestSpecification errorRequest = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
errorRequest.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
|
||||
ArrayList<HashMap> error = (ArrayList<HashMap>) this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON, errorRequest,
|
||||
errorResponse);
|
||||
// Verify we got an error response (status code may be 403 or 404 depending on environment)
|
||||
Assertions.assertNotNull(error, "Should return error for client ID used with GROUP type");
|
||||
|
||||
LOG.info("SUCCESS: Client ID with GROUP type correctly rejected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that duplicate group guarantor detection works and shows proper error message with group name
|
||||
*/
|
||||
@Test
|
||||
public void testDuplicateGroupGuarantor() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create client for loan
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(clientID);
|
||||
|
||||
// Create group with savings account
|
||||
final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
|
||||
this.savingsAccountHelper.approveSavings(savingsId);
|
||||
this.savingsAccountHelper.activateSavings(savingsId);
|
||||
|
||||
// Create loan
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null));
|
||||
|
||||
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);
|
||||
|
||||
// Add group guarantor first time - should succeed
|
||||
String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingGroupWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), GUARANTEE_AMOUNT).build();
|
||||
Integer guarantorId1 = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
|
||||
Assertions.assertNotNull(guarantorId1, "First guarantor creation should succeed");
|
||||
|
||||
// Try to add the SAME group guarantor again - should fail with duplicate error
|
||||
final ResponseSpecification errorResponse = new ResponseSpecBuilder().expectStatusCode(403).build();
|
||||
final RequestSpecification errorRequest = new RequestSpecBuilder().setContentType(ContentType.JSON).build();
|
||||
errorRequest.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
|
||||
|
||||
ArrayList<HashMap> error = (ArrayList<HashMap>) this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON, errorRequest,
|
||||
errorResponse);
|
||||
Assertions.assertNotNull(error, "Should return error for duplicate group guarantor");
|
||||
|
||||
// Verify error message contains group information
|
||||
HashMap errorData = error.get(0);
|
||||
String userMessage = (String) errorData.get("userMessageGlobalisationCode");
|
||||
Assertions.assertTrue(userMessage != null && userMessage.contains("already.exist"),
|
||||
"Error message should indicate duplicate guarantor");
|
||||
|
||||
LOG.info("SUCCESS: Duplicate group guarantor correctly rejected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test complete loan lifecycle (approval, disbursement, repayment) with a group guarantor
|
||||
*/
|
||||
@Test
|
||||
public void testGroupGuarantorLoanLifecycle() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create client for loan
|
||||
final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(clientID);
|
||||
|
||||
// Create group with savings account
|
||||
final Integer groupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(groupID);
|
||||
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(groupID, savingsProductID, ACCOUNT_TYPE_GROUP);
|
||||
this.savingsAccountHelper.approveSavings(savingsId);
|
||||
this.savingsAccountHelper.activateSavings(savingsId);
|
||||
|
||||
// Deposit funds into the savings account to cover the guarantee
|
||||
this.savingsAccountHelper.depositToSavingsAccount(savingsId, DEPOSIT_AMOUNT, SavingsAccountHelper.TRANSACTION_DATE,
|
||||
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
|
||||
// Create loan product - using minimal hold fund requirements for testing
|
||||
// Focus is on verifying that group guarantors work, not on complex hold fund logic
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal("2000").withNumberOfRepayments("1")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("0")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null));
|
||||
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("2000").withLoanTermFrequency("1")
|
||||
.withLoanTermFrequencyAsWeeks().withNumberOfRepayments("1").withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsWeeks()
|
||||
.withInterestRatePerPeriod("0").withAmortizationTypeAsEqualInstallments().withInterestTypeAsDecliningBalance()
|
||||
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod().withSubmittedOnDate(SavingsAccountHelper.TRANSACTION_DATE)
|
||||
.withExpectedDisbursementDate(SavingsAccountHelper.TRANSACTION_DATE)
|
||||
.build(clientID.toString(), loanProductID.toString(), null);
|
||||
final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
|
||||
// Add group guarantor with amount = 1000 (50% of 2000 loan)
|
||||
String guarantorJSON = new GuarantorTestBuilder()
|
||||
.existingGroupWithGuaranteeAmount(String.valueOf(groupID), String.valueOf(savingsId), "1000").build();
|
||||
Integer guarantorId = this.guarantorHelper.createGuarantor(loanID, guarantorJSON);
|
||||
Assertions.assertNotNull(guarantorId);
|
||||
|
||||
// Verify guarantor was created successfully
|
||||
ArrayList<HashMap> guarantors = this.guarantorHelper.getGuarantorList(loanID);
|
||||
Assertions.assertEquals(1, guarantors.size(), "Should have 1 group guarantor");
|
||||
HashMap guarantor = guarantors.get(0);
|
||||
HashMap guarantorType = (HashMap) guarantor.get("guarantorType");
|
||||
Assertions.assertEquals(4, guarantorType.get("id"), "Guarantor type should be GROUP (4)");
|
||||
|
||||
// Approve loan with group guarantor
|
||||
HashMap loanStatusHashMap = this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID);
|
||||
Assertions.assertNotNull(loanStatusHashMap, "Loan approval should succeed with group guarantor");
|
||||
|
||||
// Disburse loan
|
||||
this.loanTransactionHelper.disburseLoan(Long.valueOf(loanID), SavingsAccountHelper.TRANSACTION_DATE, 2000.0);
|
||||
|
||||
// Make full repayment
|
||||
final String repaymentDate = SavingsAccountHelper.TRANSACTION_DATE;
|
||||
this.loanTransactionHelper.makeRepayment(repaymentDate, Float.parseFloat("2000"), loanID);
|
||||
|
||||
LOG.info("SUCCESS: Group guarantor lifecycle test completed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test mixed client and group guarantors on the same loan
|
||||
*/
|
||||
@Test
|
||||
public void testMixedClientAndGroupGuarantors() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
// Create loan borrower client
|
||||
final Integer borrowerClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(borrowerClientID);
|
||||
|
||||
// Create guarantor client with savings
|
||||
final Integer guarantorClientID = ClientHelper.createClient(this.requestSpec, this.responseSpec);
|
||||
Assertions.assertNotNull(guarantorClientID);
|
||||
|
||||
// Create guarantor group with savings
|
||||
final Integer guarantorGroupID = GroupHelper.createGroup(this.requestSpec, this.responseSpec, true);
|
||||
Assertions.assertNotNull(guarantorGroupID);
|
||||
|
||||
// Create savings accounts
|
||||
final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, null, null,
|
||||
"false");
|
||||
|
||||
final Integer clientSavingsId = this.savingsAccountHelper.applyForSavingsApplication(guarantorClientID, savingsProductID,
|
||||
"INDIVIDUAL");
|
||||
this.savingsAccountHelper.approveSavings(clientSavingsId);
|
||||
this.savingsAccountHelper.activateSavings(clientSavingsId);
|
||||
|
||||
final Integer groupSavingsId = this.savingsAccountHelper.applyForSavingsApplication(guarantorGroupID, savingsProductID,
|
||||
ACCOUNT_TYPE_GROUP);
|
||||
this.savingsAccountHelper.approveSavings(groupSavingsId);
|
||||
this.savingsAccountHelper.activateSavings(groupSavingsId);
|
||||
|
||||
// Create loan
|
||||
LoanProductTestBuilder loanProductBuilder = new LoanProductTestBuilder().withPrincipal(PRINCIPAL).withNumberOfRepayments("4")
|
||||
.withRepaymentAfterEvery("1").withRepaymentTypeAsWeek().withinterestRatePerPeriod("2")
|
||||
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsDecliningBalance()
|
||||
.withOnHoldFundDetails("0", "0", "0");
|
||||
final Integer loanProductID = this.loanTransactionHelper.getLoanProductId(loanProductBuilder.build(null));
|
||||
|
||||
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(borrowerClientID.toString(), loanProductID.toString(), null);
|
||||
final Integer loanID = this.loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
|
||||
// Add CLIENT guarantor
|
||||
String clientGuarantorJSON = new GuarantorTestBuilder()
|
||||
.existingCustomerWithGuaranteeAmount(String.valueOf(guarantorClientID), String.valueOf(clientSavingsId), "250").build();
|
||||
Integer clientGuarantorId = this.guarantorHelper.createGuarantor(loanID, clientGuarantorJSON);
|
||||
Assertions.assertNotNull(clientGuarantorId, "Client guarantor creation should succeed");
|
||||
|
||||
// Add GROUP guarantor
|
||||
String groupGuarantorJSON = new GuarantorTestBuilder()
|
||||
.existingGroupWithGuaranteeAmount(String.valueOf(guarantorGroupID), String.valueOf(groupSavingsId), "250").build();
|
||||
Integer groupGuarantorId = this.guarantorHelper.createGuarantor(loanID, groupGuarantorJSON);
|
||||
Assertions.assertNotNull(groupGuarantorId, "Group guarantor creation should succeed");
|
||||
|
||||
// Retrieve all guarantors for the loan
|
||||
ArrayList<HashMap> guarantors = this.guarantorHelper.getGuarantorList(loanID);
|
||||
Assertions.assertNotNull(guarantors, "Should retrieve guarantor list");
|
||||
Assertions.assertEquals(2, guarantors.size(), "Should have 2 guarantors (1 client, 1 group)");
|
||||
|
||||
// Verify both guarantor types are present
|
||||
boolean hasClientGuarantor = false;
|
||||
boolean hasGroupGuarantor = false;
|
||||
|
||||
for (HashMap guarantor : guarantors) {
|
||||
HashMap guarantorType = (HashMap) guarantor.get("guarantorType");
|
||||
Integer typeId = (Integer) guarantorType.get("id");
|
||||
|
||||
if (typeId == 1) { // CUSTOMER/CLIENT
|
||||
hasClientGuarantor = true;
|
||||
} else if (typeId == 4) { // GROUP
|
||||
hasGroupGuarantor = true;
|
||||
}
|
||||
}
|
||||
|
||||
Assertions.assertTrue(hasClientGuarantor, "Should have client guarantor");
|
||||
Assertions.assertTrue(hasGroupGuarantor, "Should have group guarantor");
|
||||
|
||||
// Approve loan - both holds should be placed
|
||||
this.loanTransactionHelper.approveLoan(SavingsAccountHelper.TRANSACTION_DATE, loanID);
|
||||
|
||||
LOG.info("SUCCESS: Mixed client and group guarantors work together");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroupAccountAvailableBalance() {
|
||||
this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec);
|
||||
|
||||
+19
@@ -51,6 +51,25 @@ public class GuarantorHelper {
|
||||
CommonConstants.RESPONSE_RESOURCE_ID);
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
@Deprecated(forRemoval = true)
|
||||
public Object createGuarantorWithError(final Integer loanId, final String guarantorJSON, final RequestSpecification requestSpec,
|
||||
final ResponseSpecification responseSpec) {
|
||||
return Utils.performServerPost(requestSpec, responseSpec, LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, guarantorJSON,
|
||||
CommonConstants.RESPONSE_ERROR);
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
@Deprecated(forRemoval = true)
|
||||
public java.util.ArrayList<HashMap> getGuarantorList(final Integer loanId) {
|
||||
return (java.util.ArrayList<HashMap>) Utils.performServerGet(this.requestSpec, this.responseSpec,
|
||||
LOAN_URL + loanId + GUARANTOR_API_URL + TENANT, "");
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
|
||||
+11
-1
@@ -31,6 +31,7 @@ public class GuarantorTestBuilder {
|
||||
@SuppressWarnings("unused")
|
||||
private static final String GUARANTOR_TYPE_STAFF = "2";
|
||||
private static final String GUARANTOR_TYPE_EXTERNAL = "3";
|
||||
private static final String GUARANTOR_TYPE_GROUP = "4";
|
||||
|
||||
private String guarantorTypeId = "1";
|
||||
private String entityId = null;
|
||||
@@ -55,7 +56,7 @@ public class GuarantorTestBuilder {
|
||||
map.put("state", state);
|
||||
map.put("zip", zip);
|
||||
|
||||
} else if (GUARANTOR_TYPE_CUSTOMER.equals(guarantorTypeId)) {
|
||||
} else if (GUARANTOR_TYPE_CUSTOMER.equals(guarantorTypeId) || GUARANTOR_TYPE_GROUP.equals(guarantorTypeId)) {
|
||||
map.put("entityId", entityId);
|
||||
map.put("amount", guaranteeAmount);
|
||||
map.put("savingsId", savingsId);
|
||||
@@ -81,6 +82,15 @@ public class GuarantorTestBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public GuarantorTestBuilder existingGroupWithGuaranteeAmount(final String entityId, final String savingsId,
|
||||
final String guaranteeAmount) {
|
||||
this.entityId = entityId;
|
||||
this.savingsId = savingsId;
|
||||
this.guaranteeAmount = guaranteeAmount;
|
||||
this.guarantorTypeId = GUARANTOR_TYPE_GROUP;
|
||||
return this;
|
||||
}
|
||||
|
||||
public GuarantorTestBuilder externalCustomer() {
|
||||
this.guarantorTypeId = GUARANTOR_TYPE_EXTERNAL;
|
||||
return this;
|
||||
|
||||
Reference in New Issue
Block a user