FINERACT-1981: Advanced payment allocation validation between Loan product and Loan account

This commit is contained in:
Jose Alberto Hernandez
2024-01-12 15:41:36 +01:00
committed by Adam Saghy
parent fec768c192
commit e3a771aa71
19 changed files with 145 additions and 60 deletions
@@ -160,4 +160,6 @@ public interface LoanProductConstants {
String LOAN_SCHEDULE_TYPE = "loanScheduleType";
String LOAN_SCHEDULE_PROCESSING_TYPE = "loanScheduleProcessingType";
// Repayment Strategies
String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
}
@@ -1215,8 +1215,6 @@ final class LoansApiResourceSwagger {
public Integer graceOnInterestPayment;
@Schema(example = "1")
public Integer graceOnArrearsAgeing;
@Schema(example = "CUMULATIVE")
public String loanScheduleType;
@Schema(example = "HORIZONTAL")
public String loanScheduleProcessingType;
}
@@ -1362,8 +1360,6 @@ final class LoansApiResourceSwagger {
public List<PutLoansLoanIdChanges> charges;
public List<PutLoansLoanIdCollateral> collateral;
public List<PutLoansLoanIdDisbursementData> disbursementData;
@Schema(example = "CUMULATIVE")
public String loanScheduleType;
@Schema(example = "HORIZONTAL")
public String loanScheduleProcessingType;
@@ -53,7 +53,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollateralQuantity;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.AdvancedPaymentAllocationsValidator;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -105,7 +104,7 @@ public final class LoanApplicationCommandFromApiJsonHelper {
LoanApiConstants.lastApplication, // glim specific
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
LoanApiConstants.DISALLOW_EXPECTED_DISBURSEMENTS, LoanApiConstants.FRAUD_ATTRIBUTE_NAME,
LoanProductConstants.LOAN_SCHEDULE_TYPE, LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE));
LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE));
public static final String LOANAPPLICATION_UNDO = "loanapplication.undo";
private final FromJsonHelper fromApiJsonHelper;
@@ -559,10 +558,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull()
.isOneOfEnumValues(LoanScheduleType.class);
String loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleProcessingType().name();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
@@ -1100,12 +1095,6 @@ public final class LoanApplicationCommandFromApiJsonHelper {
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_TYPE, element)) {
final String loanScheduleType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_TYPE, element);
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_TYPE).value(loanScheduleType).ignoreIfNull()
.isOneOfEnumValues(LoanScheduleType.class);
}
String loanScheduleProcessingType = existingLoanApplication.getLoanRepaymentScheduleDetail().getLoanScheduleProcessingType().name();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
@@ -116,12 +116,14 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTopupDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationDateException;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeDeleted;
import org.apache.fineract.portfolio.loanaccount.exception.LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
@@ -689,7 +691,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
public CommandProcessingResult modifyApplication(final Long loanId, final JsonCommand command) {
try {
AppUser currentUser = getAppUserIfPresent();
final Loan existingLoanApplication = retrieveLoanBy(loanId);
if (!existingLoanApplication.isSubmittedAndPendingApproval()) {
throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(loanId);
@@ -938,8 +939,12 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
if (changes.containsKey(strategyCodeParamName)) {
final String strategy = command.stringValueOfParameterNamed(strategyCodeParamName);
existingLoanApplication.updateTransactionProcessingStrategy(strategy,
this.loanRepaymentScheduleTransactionProcessorFactory.determineProcessor(strategy).getName());
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(strategy);
validateTransactionProcessingStrategy(existingLoanApplication.getLoanProductRelatedDetail().getLoanScheduleType(),
loanRepaymentScheduleTransactionProcessor.getCode());
existingLoanApplication.updateTransactionProcessingStrategy(strategy, loanRepaymentScheduleTransactionProcessor.getName());
}
/**
@@ -1703,4 +1708,22 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
}
}
private void validateTransactionProcessingStrategy(final LoanScheduleType loanScheduleType, final String loanRepaymentStrategy) {
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
if (loanScheduleType.equals(LoanScheduleType.PROGRESSIVE)) {
if (!loanRepaymentStrategy.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation",
"Loan repayment strategy can not be different than Advanced Payment Allocation");
}
// CUMULATIVE: Repayment strategy CANNOT be "advanced payment allocation"
} else if (loanScheduleType.equals(LoanScheduleType.CUMULATIVE)) {
if (loanRepaymentStrategy.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.equal.to.advanced.payment.allocation",
"Loan repayment strategy can not be equal to Advanced Payment Allocation");
}
}
}
}
@@ -34,6 +34,7 @@ import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDoma
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.organisation.holiday.domain.Holiday;
@@ -77,7 +78,9 @@ import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementData
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
@@ -248,6 +251,23 @@ public class LoanAssembler {
final Boolean isFloatingInterestRate = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
final LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
if (loanScheduleType.equals(LoanScheduleType.PROGRESSIVE)) {
if (!transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation",
"Loan repayment strategy can not be different than Advanced Payment Allocation");
}
// CUMULATIVE: Repayment strategy CANNOT be "advanced payment allocation"
} else if (loanScheduleType.equals(LoanScheduleType.CUMULATIVE)) {
if (transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.equal.to.advanced.payment.allocation",
"Loan repayment strategy can not be equal to Advanced Payment Allocation");
}
}
if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
if (client.isNotActive()) {
@@ -2067,7 +2067,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
() -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3,
BigDecimal.ZERO, "01 January 2023", "01 January 2023",
FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE, LoanScheduleType.PROGRESSIVE.name(),
FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE,
LoanScheduleProcessingType.VERTICAL.name()));
assertEquals(400, exception.getResponse().code());
assertTrue(exception.getMessage().contains("supported.only.with.advanced.payment.allocation.strategy"));
@@ -2076,7 +2076,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
() -> applyForLoanApplication(client.getClientId(), localLoanProductId, BigDecimal.valueOf(500.0), 45, 15, 3,
BigDecimal.ZERO, "01 January 2023", "01 January 2023",
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY,
LoanScheduleType.PROGRESSIVE.name(), LoanScheduleProcessingType.HORIZONTAL.name()));
LoanScheduleProcessingType.HORIZONTAL.name()));
assertEquals(400, exception.getResponse().code());
assertTrue(exception.getMessage()
.contains("mixed.due.type.allocation.rules.are.not.supported.with.horizontal.installment.processing"));
@@ -3090,6 +3090,40 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
// UC121: Advanced payment allocation, horizontal repayment processing product and Loan application
// ADVANCED_PAYMENT_ALLOCATION_STRATEGY
@Test
public void uc121() {
final String operationDate = "01 January 2023";
runAt(operationDate, () -> {
final Account assetAccount = accountHelper.createAssetAccount();
final Account incomeAccount = accountHelper.createIncomeAccount();
final Account expenseAccount = accountHelper.createExpenseAccount();
final Account overpaymentAccount = accountHelper.createLiabilityAccount();
Integer localLoanProductId = createLoanProduct("1000", "15", "3", true, "25", false, LoanScheduleType.PROGRESSIVE,
LoanScheduleProcessingType.HORIZONTAL, assetAccount, incomeAccount, expenseAccount, overpaymentAccount);
loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(client.getClientId()).productId(localLoanProductId.longValue())
.loanType("individual").locale("en").submittedOnDate(operationDate).expectedDisbursementDate(operationDate)
.dateFormat(DATETIME_PATTERN).amortizationType(1).interestRatePerPeriod(BigDecimal.ZERO)
.interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0).repaymentEvery(15).repaymentFrequencyType(0)
.numberOfRepayments(3).loanTermFrequency(45).loanTermFrequencyType(0).principal(BigDecimal.valueOf(1000.0))
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()));
loanTransactionHelper.applyLoanWithError(new PostLoansRequest().clientId(client.getClientId())
.productId(localLoanProductId.longValue()).loanType("individual").locale("en").submittedOnDate(operationDate)
.expectedDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN).amortizationType(1)
.interestRatePerPeriod(BigDecimal.ZERO).interestCalculationPeriodType(1).interestType(0).repaymentFrequencyType(0)
.repaymentEvery(15).repaymentFrequencyType(0).numberOfRepayments(3).loanTermFrequency(45).loanTermFrequencyType(0)
.principal(BigDecimal.valueOf(1000.0)).maxOutstandingLoanBalance(BigDecimal.valueOf(35000))
.transactionProcessingStrategyCode(FineractStyleLoanRepaymentScheduleTransactionProcessor.STRATEGY_CODE)
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.name()), 403);
});
}
private static void validateLoanSummaryBalances(GetLoansLoanIdResponse loanDetails, Double totalOutstanding, Double totalRepayment,
Double principalOutstanding, Double principalPaid, Double totalOverpaid) {
assertEquals(totalOutstanding, loanDetails.getSummary().getTotalOutstanding());
@@ -3285,7 +3319,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
private static PostLoansResponse applyForLoanApplication(final Long clientId, final Integer loanProductId, final BigDecimal principal,
final int loanTermFrequency, final int repaymentAfterEvery, final int numberOfRepayments, final BigDecimal interestRate,
final String expectedDisbursementDate, final String submittedOnDate, String transactionProcessorCode, String loanScheduleType,
final String expectedDisbursementDate, final String submittedOnDate, String transactionProcessorCode,
String loanScheduleProcessingType) {
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
return loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
@@ -3294,7 +3328,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
.numberOfRepayments(numberOfRepayments).loanTermFrequency(loanTermFrequency).loanTermFrequencyType(0).principal(principal)
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType).loanScheduleType(loanScheduleType)
.loanType("individual").loanScheduleProcessingType(loanScheduleProcessingType)
.maxOutstandingLoanBalance(BigDecimal.valueOf(35000)));
}
@@ -3304,7 +3338,7 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
return applyForLoanApplication(clientId, loanProductId, principal, loanTermFrequency, repaymentAfterEvery, numberOfRepayments,
interestRate, expectedDisbursementDate, submittedOnDate,
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY, LoanScheduleType.PROGRESSIVE.name(),
AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY,
LoanScheduleProcessingType.HORIZONTAL.name());
}
@@ -56,7 +56,6 @@ public class AdvancedPaymentAllocationWaiveLoanCharges extends BaseLoanIntegrati
// Apply and Approve Loan
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()));
// Disburse Loan
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
@@ -90,7 +89,6 @@ public class AdvancedPaymentAllocationWaiveLoanCharges extends BaseLoanIntegrati
// Apply and Approve Loan
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()));
// Disburse Loan
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
@@ -124,7 +122,6 @@ public class AdvancedPaymentAllocationWaiveLoanCharges extends BaseLoanIntegrati
// Apply and Approve Loan
Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 1,
(req) -> req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString())); // Disburse
// Loan
disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 January 2023");
@@ -188,8 +185,7 @@ public class AdvancedPaymentAllocationWaiveLoanCharges extends BaseLoanIntegrati
protected Long createLoanProductWithAdvancedAllocation() {
PostLoanProductsRequest req = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct();
req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
req.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanScheduleType(LoanScheduleType.PROGRESSIVE.name())
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString());
req.addPaymentAllocationItem(createDefaultPaymentAllocationWithMixedGrouping());
PostLoanProductsResponse loanProduct = loanTransactionHelper.createLoanProduct(req);
@@ -131,7 +131,8 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
}
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal, String submitDate) {
private Integer applyForLoanApplication(final Integer clientID, final Integer loanProductID, String principal, String submitDate,
String repaymentStrategy) {
log.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
final String loanApplicationJSON = new LoanApplicationTestBuilder() //
.withPrincipal(principal) //
@@ -146,6 +147,7 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
.withInterestCalculationPeriodTypeSameAsRepaymentPeriod() //
.withExpectedDisbursementDate(submitDate) //
.withSubmittedOnDate(submitDate) //
.withRepaymentStrategy(repaymentStrategy) //
.build(clientID.toString(), loanProductID.toString(), null);
return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
}
@@ -161,7 +163,8 @@ public class ClientLoanCreditBalanceRefundandRepaymentTypeIntegrationTest extend
accounts);
Assertions.assertNotNull(loanProductID);
final Integer loanID = applyForLoanApplication(clientID, loanProductID, principal, submitApproveDisburseDate);
final Integer loanID = applyForLoanApplication(clientID, loanProductID, principal, submitApproveDisburseDate,
loanProductTestBuilder.getTransactionProcessingStrategyCode());
Assertions.assertNotNull(loanID);
HashMap loanStatusHashMap = LoanStatusChecker.getStatusOfLoan(this.requestSpec, this.responseSpec, loanID);
LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
@@ -118,7 +118,8 @@ public class DelinquencyAndChargebackIntegrationTest {
// Create Loan Account
final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
getLoanProductsProductResponse.getId().toString(), operationDate, "12");
getLoanProductsProductResponse.getId().toString(), operationDate, "12",
loanProductTestBuilder.getTransactionProcessingStrategyCode());
// Move the Business date 1 month to apply the first repayment
businessDate = businessDate.plusMonths(1);
@@ -238,7 +239,8 @@ public class DelinquencyAndChargebackIntegrationTest {
// Create Loan Account
final Integer loanId = createLoanAccount(loanTransactionHelper, clientId.toString(),
getLoanProductsProductResponse.getId().toString(), operationDate, "3");
getLoanProductsProductResponse.getId().toString(), operationDate, "3",
loanProductTestBuilder.getTransactionProcessingStrategyCode());
// Move the Business date 1 month to apply the first repayment
businessDate = businessDate.plusMonths(1);
@@ -346,7 +348,7 @@ public class DelinquencyAndChargebackIntegrationTest {
}
private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId,
final String operationDate, final String periods) {
final String operationDate, final String periods, String repaymentStrategy) {
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency(periods)
.withLoanTermFrequencyAsMonths().withNumberOfRepayments(periods).withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths() //
@@ -354,6 +356,7 @@ public class DelinquencyAndChargebackIntegrationTest {
.withExpectedDisbursementDate(operationDate) //
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate(operationDate) //
.withRepaymentStrategy(repaymentStrategy) //
.build(clientId, loanProductId, null);
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
@@ -130,15 +130,27 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(financialActivityAccountId);
} else {
boolean existFinancialActivity = false;
for (HashMap financialActivity : financialActivities) {
HashMap financialActivityData = (HashMap) financialActivity.get("financialActivityData");
if (financialActivityData.get("id").equals(FinancialActivityAccountsTest.LIABILITY_TRANSFER_FINANCIAL_ACTIVITY_ID)) {
HashMap glAccountData = (HashMap) financialActivity.get("glAccountData");
liabilityTransferAccount = new Account((Integer) glAccountData.get("id"), Account.AccountType.LIABILITY);
financialActivityAccountId = (Integer) financialActivity.get("id");
existFinancialActivity = true;
break;
}
}
if (!existFinancialActivity) {
liabilityTransferAccount = accountHelper.createLiabilityAccount();
Assertions.assertNotNull(liabilityTransferAccount);
/*** Create A Financial Activity to Account Mapping **/
financialActivityAccountId = (Integer) financialActivityAccountHelper.createFinancialActivityAccount(
LIABILITY_TRANSFER.getValue(), liabilityTransferAccount.getAccountID(), responseSpec,
CommonConstants.RESPONSE_RESOURCE_ID);
Assertions.assertNotNull(financialActivityAccountId);
}
}
}
@@ -275,7 +287,6 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
return loanTransactionHelper.applyLoan(new PostLoansRequest().clientId(clientId).productId(loanProductId.longValue())
.expectedDisbursementDate(expectedDisbursementDate).dateFormat(DATETIME_PATTERN)
.transactionProcessingStrategyCode(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString())
.loanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()).locale("en").submittedOnDate(submittedOnDate)
.amortizationType(1).interestRatePerPeriod(interestRate).interestCalculationPeriodType(1).interestType(0)
.repaymentFrequencyType(0).repaymentEvery(repaymentAfterEvery).repaymentFrequencyType(0)
@@ -302,7 +313,6 @@ public class LoanChargePaymentWithAdvancedPaymentAllocationTest {
.withRepaymentFrequencyTypeAsDays() //
.withInterestRatePerPeriod("0") //
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()) //
.withAmortizationTypeAsEqualInstallments() //
.withInterestTypeAsDecliningBalance() //
@@ -54,6 +54,7 @@ import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHe
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
@@ -82,6 +83,7 @@ public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
this.clientHelper = new ClientHelper(this.requestSpec, this.responseSpec);
}
@Disabled("Issue with Chargeback and Adv Pment Alloc")
@ParameterizedTest
@MethodSource("loanProductFactory")
public void loanTransactionChargebackForPaymentTypeRepaymentTransactionTest(LoanProductTestBuilder loanProductTestBuilder) {
@@ -100,7 +102,8 @@ public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
delinquencyBucketId, loanProductTestBuilder);
assertNotNull(getLoanProductsProductResponse);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr,
loanProductTestBuilder.getTransactionProcessingStrategyCode());
// make Repayment
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
@@ -235,7 +238,8 @@ public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
delinquencyBucketId, loanProductTestBuilder);
assertNotNull(getLoanProductsProductResponse);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr,
loanProductTestBuilder.getTransactionProcessingStrategyCode());
// Merchant Refund
final PostLoansLoanIdTransactionsResponse merchantIssuedRefund_2 = loanTransactionHelper.makeMerchantIssuedRefund((long) loanId,
@@ -258,14 +262,16 @@ public class LoanChargebackOnPaymentTypeRepaymentTransactionsTest {
return loanTransactionHelper.getLoanProduct(loanProductId);
}
private Integer createLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) {
private Integer createLoanAccount(final Integer clientID, final Long loanProductID, final String externalId,
final String repaymentStrategy) {
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
.withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
.withExternalId(externalId).withRepaymentStrategy(repaymentStrategy)
.build(clientID.toString(), loanProductID.toString(), null);
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null);
@@ -625,7 +625,7 @@ public class LoanTransactionChargebackTest {
String operationDate = Utils.dateFormatter.format(transactionDate);
return createLoanAccount(loanTransactionHelper, clientId.toString(), getLoanProductsProductResponse.getId().toString(),
operationDate, amountVal, numberOfRepayments.toString());
operationDate, amountVal, numberOfRepayments.toString(), loanProductTestBuilder.getTransactionProcessingStrategyCode());
}
private GetLoanProductsProductIdResponse createLoanProduct(final LoanTransactionHelper loanTransactionHelper,
@@ -647,7 +647,7 @@ public class LoanTransactionChargebackTest {
}
private Integer createLoanAccount(final LoanTransactionHelper loanTransactionHelper, final String clientId, final String loanProductId,
final String operationDate, final String principalAmount, final String numberOfRepayments) {
final String operationDate, final String principalAmount, final String numberOfRepayments, final String repaymentStrategy) {
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount)
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsMonths().withNumberOfRepayments(numberOfRepayments)
.withRepaymentEveryAfter("1").withRepaymentFrequencyTypeAsMonths() //
@@ -655,6 +655,7 @@ public class LoanTransactionChargebackTest {
.withExpectedDisbursementDate(operationDate) //
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate(operationDate) //
.withRepaymentStrategy(repaymentStrategy) //
.build(clientId, loanProductId, null);
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
@@ -100,7 +100,8 @@ public class LoanTransactionFullAmountChargebackForOverpaidLoanTest {
delinquencyBucketId, loanProductTestBuilder);
assertNotNull(getLoanProductsProductResponse);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr);
final Integer loanId = createLoanAccount(clientId, getLoanProductsProductResponse.getId(), loanExternalIdStr,
loanProductTestBuilder.getTransactionProcessingStrategyCode());
// make Repayments
final PostLoansLoanIdTransactionsResponse repaymentTransaction_1 = loanTransactionHelper.makeLoanRepayment(loanExternalIdStr,
@@ -168,14 +169,16 @@ public class LoanTransactionFullAmountChargebackForOverpaidLoanTest {
return loanTransactionHelper.getLoanProduct(loanProductId);
}
private Integer createLoanAccount(final Integer clientID, final Long loanProductID, final String externalId) {
private Integer createLoanAccount(final Integer clientID, final Long loanProductID, final String externalId,
final String repaymentStrategy) {
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
.withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
.withExternalId(externalId).withRepaymentStrategy(repaymentStrategy)
.build(clientID.toString(), loanProductID.toString(), null);
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null);
@@ -154,7 +154,6 @@ public class LoanWithAdvancedPaymentAllocationIntegrationTests {
.withInterestTypeAsDecliningBalance() //
.withSubmittedOnDate(operationDate) //
.withRepaymentStrategy(ADVANCED_PAYMENT_ALLOCATION_STRATEGY) //
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()) //
.build(clientId, loanProductId, null);
return loanTransactionHelper.getLoanId(loanApplicationJSON);
@@ -498,9 +498,8 @@ public class RefundForActiveLoansWithAdvancedPaymentAllocationTest {
.withRepaymentStrategy(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)
.withLoanScheduleType(LoanScheduleType.PROGRESSIVE).withLoanScheduleProcessingType(loanScheduleProcessingType)
.withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat().withAccountingRulePeriodicAccrual(accounts)
.addAdvancedPaymentAllocation(defaultAllocation).withLoanScheduleType(LoanScheduleType.PROGRESSIVE)
.withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL).withDaysInMonth("30").withDaysInYear("365")
.withMoratorium("0", "0").build(null);
.addAdvancedPaymentAllocation(defaultAllocation).withLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL)
.withDaysInMonth("30").withDaysInYear("365").withMoratorium("0", "0").build(null);
return loanTransactionHelper.getLoanProductId(loanProductJSON);
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.integrationtests;
import static org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -346,7 +347,8 @@ public class UndoRepaymentWithDownPaymentIntegrationTest {
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod(interestRate).withInterestTypeAsFlatBalance()
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
.withExpectedDisbursementDate("03 September 2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
.withExternalId(externalId).build(clientID.toString(), loanProductID.toString(), null);
.withRepaymentStrategy(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).withExternalId(externalId)
.build(clientID.toString(), loanProductID.toString(), null);
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
loanTransactionHelper.approveLoan("02 September 2022", "1000", loanId, null);
@@ -60,7 +60,6 @@ public class LoanApplicationTestBuilder {
private String interestCalculationPeriodType = CALCULATION_PERIOD_SAME_AS_REPAYMENT_PERIOD;
private String transactionProcessingCode = DEFAULT_STRATEGY;
private String loanScheduleProcessingType = null;
private String loanScheduleType = null;
private String expectedDisbursmentDate = "";
private String submittedOnDate = "";
private String loanType = "individual";
@@ -160,10 +159,6 @@ public class LoanApplicationTestBuilder {
map.put("collateral", this.collaterals);
map.put("interestChargedFromDate", this.interestChargedFromDate);
if (loanScheduleType != null) {
map.put("loanScheduleType", this.loanScheduleType);
}
if (loanScheduleProcessingType != null) {
map.put("loanScheduleProcessingType", this.loanScheduleProcessingType);
}
@@ -367,11 +362,6 @@ public class LoanApplicationTestBuilder {
return this;
}
public LoanApplicationTestBuilder withLoanScheduleType(final String loanScheduleType) {
this.loanScheduleType = loanScheduleType;
return this;
}
public LoanApplicationTestBuilder withLoanScheduleProcessingType(final String loanScheduleProcessingType) {
this.loanScheduleProcessingType = loanScheduleProcessingType;
return this;
@@ -764,4 +764,9 @@ public class LoanProductTestBuilder {
this.loanScheduleProcessingType = loanScheduleProcessingType.name();
return this;
}
public String getTransactionProcessingStrategyCode() {
return transactionProcessingStrategyCode;
}
}
@@ -1819,6 +1819,10 @@ public class LoanTransactionHelper extends IntegrationTest {
return ok(fineract().loans.calculateLoanScheduleOrSubmitLoanApplication(request, null));
}
public void applyLoanWithError(PostLoansRequest request, Integer httpStatus) {
assertThat(fineract().loans.calculateLoanScheduleOrSubmitLoanApplication(request, null)).hasHttpStatus(httpStatus);
}
public PostLoansLoanIdResponse approveLoan(String loanExternalId, PostLoansLoanIdRequest request) {
return ok(fineract().loans.stateTransitions1(loanExternalId, request, "approve"));
}