Skip to content

Commit

Permalink
FINERACT-1981: Fix progressive loan undo previous repayment
Browse files Browse the repository at this point in the history
  • Loading branch information
magyari-adam committed Oct 7, 2024
1 parent a7965a7 commit e017796
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3377,3 +3377,21 @@ Feature: LoanRepayment
| | | 23 July 2024 | | 111.92 | | | 0.0 | | 0.0 | 0.0 | | | |
| 1 | 31 | 23 August 2024 | 23 August 2024 | 0.0 | 111.92 | 0.0 | 0.0 | 5.0 | 116.92 | 116.92 | 114.72 | 0.0 | 0.0 |
| 2 | 2 | 25 August 2024 | 23 August 2024 | 0.0 | 0.0 | 0.0 | 0.0 | 7.8 | 7.8 | 7.8 | 7.8 | 0.0 | 0.0 |

@AdvancedPaymentAllocation @ProgressiveLoanSchedule
Scenario: Progressive loan, undo earlier repayment
Given Global configuration "enable-business-date" is enabled
When Admin sets the business date to "23 June 2024"
When Admin creates a client with random data
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_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_TILL_PRECLOSE | 23 June 2024 | 400 | 7.0 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
And Admin successfully approves the loan on "23 June 2024" with "400" amount and expected disbursement date on "23 June 2024"
When Admin successfully disburse the loan on "23 June 2024" with "400" EUR transaction amount
When Admin sets the business date to "24 June 2024"
And Customer makes "AUTOPAY" repayment on "24 June 2024" with 100 EUR transaction amount
When Admin sets the business date to "10 September 2024"
And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "10 September 2024" with 400 EUR transaction amount and self-generated Idempotency key
When Admin makes Credit Balance Refund transaction on "10 September 2024" with 91.21 EUR transaction amount
When Customer undo "1"th "Repayment" transaction made on "24 June 2024"
Then Loan status will be "ACTIVE"
Original file line number Diff line number Diff line change
Expand Up @@ -2665,12 +2665,6 @@ public ChangedTransactionDetail adjustExistingTransaction(final LoanTransaction
final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction transactionForAdjustment,
final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
final ScheduleGeneratorDTO scheduleGeneratorDTO, final ExternalId reversalExternalId) {
HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
validateActivityNotBeforeLastTransactionDate(LoanEvent.LOAN_REPAYMENT_OR_WAIVER, transactionForAdjustment.getTransactionDate());
validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(), holidayDetailDTO.isAllowTransactionsOnHoliday(),
holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(), holidayDetailDTO.getWorkingDays(),
holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());

ChangedTransactionDetail changedTransactionDetail = null;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
Expand Down Expand Up @@ -776,15 +777,34 @@ public void validateClientOfficeJoiningDateIsBeforeTransactionDate(Loan loan, Lo
}
}

public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate) {
public void validateActivityNotBeforeLastTransactionDate(final Loan loan, final LocalDate activityDate, final LoanEvent event) {
if (!(loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || loan.loanProduct().isHoldGuaranteeFunds())) {
return;
}
LocalDate lastTransactionDate = loan.getLastUserTransactionDate();
if (DateUtils.isAfter(lastTransactionDate, activityDate)) {
String errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date";
String action = "repayment.or.waiver";
String postfix = "cannot.be.made.before.last.transaction.date";
String errorMessage = null;
String action = null;
String postfix = null;
switch (event) {
case LOAN_REPAYMENT_OR_WAIVER -> {
errorMessage = "The date on which a repayment or waiver is made cannot be earlier than last transaction date";
action = "repayment.or.waiver";
postfix = "cannot.be.made.before.last.transaction.date";
}
case WRITE_OFF_OUTSTANDING -> {
errorMessage = "The date on which a write off is made cannot be earlier than last transaction date";
action = "writeoff";
postfix = "cannot.be.made.before.last.transaction.date";
}
case LOAN_CHARGE_PAYMENT -> {
errorMessage = "The date on which a charge payment is made cannot be earlier than last transaction date";
action = "charge.payment";
postfix = "cannot.be.made.before.last.transaction.date";
}
default -> {
}
}
throw new InvalidLoanStateTransitionException(action, postfix, errorMessage, lastTransactionDate);
}
}
Expand Down Expand Up @@ -831,7 +851,7 @@ public void validateLoanTransactionInterestPaymentWaiver(JsonCommand command) {
validateLoanHasNoLaterChargeRefundTransactionToReverseOrCreateATransaction(loan, transactionDate, "created");

validateClientOfficeJoiningDateIsBeforeTransactionDate(loan, transactionDate);
validateActivityNotBeforeLastTransactionDate(loan, transactionDate);
validateActivityNotBeforeLastTransactionDate(loan, transactionDate, LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
HolidayDetailDTO holidayDetailDTO = loanUtilService.constructHolidayDTO(loan.getOfficeId(), loan.getDisbursementDate());
validateRepaymentDateIsOnHoliday(transactionDate, holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
validateRepaymentDateIsOnNonWorkingDay(transactionDate, holidayDetailDTO.getWorkingDays(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
Expand Down Expand Up @@ -1565,6 +1566,18 @@ public CommandProcessingResult adjustLoanTransaction(final Long loanId, final Lo

ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);

HolidayDetailDTO holidayDetailDTO = scheduleGeneratorDTO.getHolidayDetailDTO();
if (loan.getLoanRepaymentScheduleDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
// validate cumulative
loanTransactionValidator.validateActivityNotBeforeLastTransactionDate(loan, transactionToAdjust.getTransactionDate(),
LoanEvent.LOAN_REPAYMENT_OR_WAIVER);
}
// common validations
loanTransactionValidator.validateRepaymentDateIsOnHoliday(newTransactionDetail.getTransactionDate(),
holidayDetailDTO.isAllowTransactionsOnHoliday(), holidayDetailDTO.getHolidays());
loanTransactionValidator.validateRepaymentDateIsOnNonWorkingDay(newTransactionDetail.getTransactionDate(),
holidayDetailDTO.getWorkingDays(), holidayDetailDTO.isAllowTransactionsOnNonWorkingDay());

final ChangedTransactionDetail changedTransactionDetail = loan.adjustExistingTransaction(newTransactionDetail,
loanLifecycleStateMachine, transactionToAdjust, existingTransactionIds, existingReversedTransactionIds,
scheduleGeneratorDTO, reversalTxnExternalId);
Expand Down

0 comments on commit e017796

Please sign in to comment.