FINERACT-2081: Enable inline COB execution for locked loans
This commit is contained in:
committed by
Adam Saghy
parent
2e89c40e6e
commit
240a6dffc8
+38
@@ -555,6 +555,44 @@ public class BatchApiStepDef extends AbstractStepDef {
|
||||
log.debug("ERROR MESSAGE: {}", errorMessageActual);
|
||||
}
|
||||
|
||||
@When("Batch API call with created user and the following data results a {int} statuscode WITHOUT error message:")
|
||||
public void runBatchApiCreateAndApproveLoanRescheduleWithGivenUserLockedByCobWithoutError(int httpCodeExpected, DataTable table)
|
||||
throws IOException {
|
||||
String idempotencyKey = UUID.randomUUID().toString();
|
||||
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
|
||||
Long loanId = loanResponse.body().getLoanId();
|
||||
|
||||
List<List<String>> data = table.asLists();
|
||||
List<String> transferData = data.get(1);
|
||||
String fromDateStr = transferData.get(0);
|
||||
String submittedOnDate = transferData.get(1);
|
||||
String toDateStr = transferData.get(2);
|
||||
String approvedOnDate = transferData.get(3);
|
||||
String enclosingTransaction = transferData.get(4);
|
||||
|
||||
Map<String, String> headerMap = new HashMap<>();
|
||||
|
||||
Response<PostUsersResponse> createUserResponse = testContext().get(TestContextKey.CREATED_SIMPLE_USER_RESPONSE);
|
||||
Long createdUserId = createUserResponse.body().getResourceId();
|
||||
Response<GetUsersUserIdResponse> user = usersApi.retrieveOne31(createdUserId).execute();
|
||||
ErrorHelper.checkSuccessfulApiCall(user);
|
||||
String authorizationString = user.body().getUsername() + ":" + PWD_USER_WITH_ROLE;
|
||||
Base64 base64 = new Base64();
|
||||
headerMap.put("Authorization",
|
||||
"Basic " + new String(base64.encode(authorizationString.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8));
|
||||
List<BatchRequest> requestList = new ArrayList<>();
|
||||
requestList.add(createLoanReschedule(1L, loanId, fromDateStr, toDateStr, submittedOnDate, idempotencyKey, null));
|
||||
requestList.add(approveLoanReschedule(2L, idempotencyKey, approvedOnDate, 1L));
|
||||
|
||||
Boolean isEnclosingTransaction = Boolean.valueOf(enclosingTransaction);
|
||||
Response<List<BatchResponse>> batchResponseList = batchApiApi.handleBatchRequests(requestList, isEnclosingTransaction, headerMap)
|
||||
.execute();
|
||||
BatchResponse lastBatchResponse = batchResponseList.body().get(batchResponseList.body().size() - 1);
|
||||
assertThat(httpCodeExpected).isEqualTo(lastBatchResponse.getStatusCode());
|
||||
// No error
|
||||
assertThat(batchResponseList.errorBody()).isEqualTo(null);
|
||||
}
|
||||
|
||||
@When("Batch API call with steps: queryDatatable, updateDatatable runs, with empty queryDatatable response")
|
||||
public void runBatchApiQueryDatatableUpdateDatatable() throws IOException {
|
||||
String idempotencyKey = UUID.randomUUID().toString();
|
||||
|
||||
@@ -659,7 +659,30 @@ Feature: LoanReschedule
|
||||
Then Admin checks that last closed business date of loan is "09 January 2024"
|
||||
|
||||
@TestRailId:C3048 @AdvancedPaymentAllocation
|
||||
Scenario: Verify that in case of Loan is hard locked for COB execution, BatchAPI request of Loan reschedule creation and approval will result a 409 error and a LOAN_LOCKED_BY_COB error message
|
||||
Scenario: Verify that in case of Loan is hard locked for COB execution without error message, BatchAPI request of Loan reschedule creation and approval will result a 409 error and a LOAN_LOCKED_BY_COB error message
|
||||
When Admin sets the business date to "01 January 2024"
|
||||
When Admin creates a client with random data
|
||||
When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
||||
When Admin creates a fully customized loan with the following data:
|
||||
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
|
||||
| LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION | 01 January 2024 | 1000 | 0 | FLAT | SAME_AS_REPAYMENT_PERIOD | EQUAL_INSTALLMENTS | 45 | DAYS | 15 | DAYS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
|
||||
And Admin successfully approves the loan on "01 January 2024" with "1000" amount and expected disbursement date on "01 January 2024"
|
||||
When Admin successfully disburse the loan on "01 January 2024" with "1000" EUR transaction amount
|
||||
When Admin sets the business date to "02 January 2024"
|
||||
When Admin runs inline COB job for Loan
|
||||
When Admin sets the business date to "10 January 2024"
|
||||
When Admin places a lock on loan account WITHOUT an error message
|
||||
When Admin creates new user with "NO_BYPASS_AUTOTEST" username, "NO_BYPASS_AUTOTEST_ROLE" role name and given permissions:
|
||||
| APPROVE_RESCHEDULELOAN |
|
||||
| CREATE_RESCHEDULELOAN |
|
||||
| READ_RESCHEDULELOAN |
|
||||
| REJECT_RESCHEDULELOAN |
|
||||
When Batch API call with created user and the following data results a 409 error and a "LOAN_LOCKED_BY_COB" error message:
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | approvedOnDate | enclosingTransaction |
|
||||
| 16 January 2024 | 10 January 2024 | 31 January 2024 | 10 January 2024 | true |
|
||||
|
||||
@TestRailId:C3049 @AdvancedPaymentAllocation
|
||||
Scenario: Verify that in case of Loan is hard locked for COB execution with error message, BatchAPI request of Loan reschedule creation and approval will result a 200 statuscode without error message
|
||||
When Admin sets the business date to "01 January 2024"
|
||||
When Admin creates a client with random data
|
||||
When Admin set "LP2_DOWNPAYMENT_AUTO_ADVANCED_PAYMENT_ALLOCATION" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule
|
||||
@@ -677,6 +700,6 @@ Feature: LoanReschedule
|
||||
| CREATE_RESCHEDULELOAN |
|
||||
| READ_RESCHEDULELOAN |
|
||||
| REJECT_RESCHEDULELOAN |
|
||||
When Batch API call with created user and the following data results a 409 error and a "LOAN_LOCKED_BY_COB" error message:
|
||||
When Batch API call with created user and the following data results a 200 statuscode WITHOUT error message:
|
||||
| rescheduleFromDate | submittedOnDate | adjustedDueDate | approvedOnDate | enclosingTransaction |
|
||||
| 16 January 2024 | 10 January 2024 | 31 January 2024 | 10 January 2024 | true |
|
||||
|
||||
+2
@@ -36,6 +36,8 @@ public interface LoanAccountLockRepository
|
||||
|
||||
boolean existsByLoanIdAndLockOwner(Long loanId, LockOwner lockOwner);
|
||||
|
||||
boolean existsByLoanIdAndLockOwnerAndErrorIsNotNull(Long loanId, LockOwner lockOwner);
|
||||
|
||||
@Query("""
|
||||
delete from LoanAccountLock lck where lck.lockPlacedOnCobBusinessDate is not null and lck.error is not null and
|
||||
lck.lockOwner in (org.apache.fineract.cob.domain.LockOwner.LOAN_COB_CHUNK_PROCESSING,org.apache.fineract.cob.domain.LockOwner.LOAN_INLINE_COB_PROCESSING)
|
||||
|
||||
+2
@@ -27,5 +27,7 @@ public interface LoanAccountLockService {
|
||||
|
||||
boolean isLoanHardLocked(Long loanId);
|
||||
|
||||
boolean isLockOverrulable(Long loanId);
|
||||
|
||||
void updateCobAndRemoveLocks();
|
||||
}
|
||||
|
||||
+6
@@ -49,6 +49,12 @@ public class LoanAccountLockServiceImpl implements LoanAccountLockService {
|
||||
|| loanAccountLockRepository.existsByLoanIdAndLockOwner(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLockOverrulable(Long loanId) {
|
||||
return loanAccountLockRepository.existsByLoanIdAndLockOwnerAndErrorIsNotNull(loanId, LockOwner.LOAN_COB_CHUNK_PROCESSING) //
|
||||
|| loanAccountLockRepository.existsByLoanIdAndLockOwnerAndErrorIsNotNull(loanId, LockOwner.LOAN_INLINE_COB_PROCESSING);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||
public void updateCobAndRemoveLocks() {
|
||||
|
||||
+10
-2
@@ -186,6 +186,14 @@ public class LoanCOBFilterHelper implements InitializingBean {
|
||||
return loanIds.stream().anyMatch(loanAccountLockService::isLoanHardLocked);
|
||||
}
|
||||
|
||||
private boolean isLockOverrulable(Long... loanIds) {
|
||||
return isLockOverrulable(Arrays.asList(loanIds));
|
||||
}
|
||||
|
||||
private boolean isLockOverrulable(List<Long> loanIds) {
|
||||
return loanIds.stream().anyMatch(loanAccountLockService::isLockOverrulable);
|
||||
}
|
||||
|
||||
public boolean isLoanBehind(List<Long> loanIds) {
|
||||
List<LoanIdAndLastClosedBusinessDate> loanIdAndLastClosedBusinessDates = new ArrayList<>();
|
||||
List<List<Long>> partitions = Lists.partition(loanIds, fineractProperties.getQuery().getInClauseParameterSizeLimit());
|
||||
@@ -217,7 +225,7 @@ public class LoanCOBFilterHelper implements InitializingBean {
|
||||
// check the body for Loan ID
|
||||
Long loanId = getTopLevelLoanIdFromBatchRequest(batchRequest);
|
||||
if (loanId != null) {
|
||||
if (isLoanHardLocked(loanId)) {
|
||||
if (isLoanHardLocked(loanId) && !isLockOverrulable(loanId)) {
|
||||
throw new LoanIdsHardLockedException(loanId);
|
||||
} else {
|
||||
loanIds.add(loanId);
|
||||
@@ -240,7 +248,7 @@ public class LoanCOBFilterHelper implements InitializingBean {
|
||||
|
||||
private List<Long> getLoanIdsFromApi(String pathInfo) {
|
||||
List<Long> loanIds = getLoanIdList(pathInfo);
|
||||
if (isLoanHardLocked(loanIds)) {
|
||||
if (isLoanHardLocked(loanIds) && !isLockOverrulable(loanIds)) {
|
||||
throw new LoanIdsHardLockedException(loanIds.get(0));
|
||||
} else {
|
||||
return loanIds;
|
||||
|
||||
+71
@@ -37,6 +37,7 @@ import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -80,6 +81,9 @@ import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAcc
|
||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
|
||||
import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
|
||||
import org.apache.fineract.integrationtests.useradministration.users.UserHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.EarlyPaymentLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.FineractStyleLoanRepaymentScheduleTransactionProcessor;
|
||||
@@ -5709,6 +5713,73 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uc152() {
|
||||
AtomicLong createdLoanId = new AtomicLong();
|
||||
runAt("01 January 2024", () -> {
|
||||
Long clientId = client.getClientId();
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation()
|
||||
.interestRateFrequencyType(YEARS).numberOfRepayments(4)//
|
||||
.maxInterestRatePerPeriod((double) 0)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(1L)//
|
||||
.allowPartialPeriodInterestCalcualtion(false)//
|
||||
.multiDisburseLoan(false)//
|
||||
.disallowExpectedDisbursements(null)//
|
||||
.allowApprovedDisbursedAmountsOverApplied(null)//
|
||||
.overAppliedCalculationType(null)//
|
||||
.overAppliedNumber(null)//
|
||||
.installmentAmountInMultiplesOf(null)//
|
||||
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
|
||||
;//
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), "01 January 2024", 400.0,
|
||||
6);
|
||||
|
||||
applicationRequest = applicationRequest.interestCalculationPeriodType(DAYS).interestRatePerPeriod(BigDecimal.ZERO)
|
||||
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||
|
||||
PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
createdLoanId.set(loanResponse.getLoanId());
|
||||
|
||||
loanTransactionHelper.approveLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(400.0)).dateFormat(DATETIME_PATTERN)
|
||||
.approvedOnDate("01 January 2024").locale("en"));
|
||||
|
||||
loanTransactionHelper.disburseLoan(loanResponse.getLoanId(),
|
||||
new PostLoansLoanIdRequest().actualDisbursementDate("01 January 2024").dateFormat(DATETIME_PATTERN)
|
||||
.transactionAmount(BigDecimal.valueOf(400.0)).locale("en"));
|
||||
});
|
||||
|
||||
runAt("02 January 2024", () -> {
|
||||
executeInlineCOB(createdLoanId.get());
|
||||
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(LocalDate.of(2024, 1, 1), loanDetails.getLastClosedBusinessDate());
|
||||
final String errorMessage = Utils.uniqueRandomStringGenerator("error.", 40);
|
||||
placeHardLockOnLoan(createdLoanId.get(), errorMessage);
|
||||
});
|
||||
|
||||
runAt("03 January 2024", () -> {
|
||||
Integer roleId = RolesHelper.createRole(requestSpec, responseSpec);
|
||||
Map<String, Boolean> permissionMap = Map.of("REPAYMENT_LOAN", true);
|
||||
RolesHelper.addPermissionsToRole(requestSpec, responseSpec, roleId, permissionMap);
|
||||
final Integer staffId = StaffHelper.createStaff(this.requestSpec, this.responseSpec);
|
||||
|
||||
final String operatorUser = Utils.uniqueRandomStringGenerator("user", 8);
|
||||
UserHelper.createUser(this.requestSpec, this.responseSpec, roleId, staffId, operatorUser, UserHelper.SIMPLE_USER_PASSWORD,
|
||||
"resourceId");
|
||||
|
||||
loanTransactionHelper.makeLoanRepayment(
|
||||
createdLoanId.get(), new PostLoansLoanIdTransactionsRequest().transactionDate("03 January 2024")
|
||||
.dateFormat("dd MMMM yyyy").locale("en").transactionAmount(200.0),
|
||||
operatorUser, UserHelper.SIMPLE_USER_PASSWORD);
|
||||
|
||||
final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get());
|
||||
assertEquals(LocalDate.of(2024, 1, 2), loanDetails.getLastClosedBusinessDate());
|
||||
});
|
||||
}
|
||||
|
||||
private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId,
|
||||
Integer numberOfRepayments, String loanDisbursementDate, double amount) {
|
||||
LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------");
|
||||
|
||||
+4
@@ -600,6 +600,10 @@ public abstract class BaseLoanIntegrationTest {
|
||||
loanAccountLockHelper.placeSoftLockOnLoanAccount(loanId.intValue(), "LOAN_COB_CHUNK_PROCESSING");
|
||||
}
|
||||
|
||||
protected void placeHardLockOnLoan(Long loanId, String error) {
|
||||
loanAccountLockHelper.placeSoftLockOnLoanAccount(loanId.intValue(), "LOAN_COB_CHUNK_PROCESSING", error);
|
||||
}
|
||||
|
||||
protected void executeInlineCOB(Long loanId) {
|
||||
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId));
|
||||
}
|
||||
|
||||
+5
@@ -602,6 +602,11 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final PostLoansLoanIdTransactionsRequest request,
|
||||
final String user, final String pass) {
|
||||
return ok(newFineract(user, pass).loanTransactions.executeLoanTransaction(loanId, request, "repayment"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse makeInterestPaymentWaiver(final Long loanId,
|
||||
final PostLoansLoanIdTransactionsRequest request) {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction(loanId, request, "interestPaymentWaiver"));
|
||||
|
||||
Reference in New Issue
Block a user