Skip to content

Commit

Permalink
FINERACT-2081: Enable inline COB execution for locked loans
Browse files Browse the repository at this point in the history
  • Loading branch information
Jose Alberto Hernandez committed Nov 18, 2024
1 parent 7408f10 commit 0d60d5d
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,7 @@ public interface LoanAccountLockService {

boolean isLoanHardLocked(Long loanId);

boolean isLockOverrulable(Long loanId);

void updateCobAndRemoveLocks();
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public boolean isLoanHardLocked(Long loanId) {
|| 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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ private boolean isLoanHardLocked(List<Long> loanIds) {
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());
Expand Down Expand Up @@ -217,7 +225,7 @@ private List<Long> getLoanIdsFromBatchApi(BodyCachingHttpServletRequestWrapper r
// 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);
Expand All @@ -240,7 +248,7 @@ private Long getTopLevelLoanIdFromBatchRequest(BatchRequest batchRequest) throws

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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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;
Expand Down Expand Up @@ -80,6 +81,9 @@
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;
Expand Down Expand Up @@ -5709,6 +5713,73 @@ public void uc151() {

}

@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 ---------------------------------------");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,10 @@ protected void placeHardLockOnLoan(Long loanId) {
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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,11 @@ public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId,
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"));
Expand Down

0 comments on commit 0d60d5d

Please sign in to comment.