Skip to content

Commit

Permalink
FINERACT-2081: clear closed_on date on repayment reversal
Browse files Browse the repository at this point in the history
  • Loading branch information
kjozsa committed Aug 8, 2024
1 parent 81bf50d commit d87defc
Show file tree
Hide file tree
Showing 14 changed files with 292 additions and 353 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static LocalDate getOffSetDateIfNonWorkingDay(final LocalDate date, final
}

public static boolean isWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
return CalendarUtils.isValidRecurringDate(workingDays.getRecurrence(), date, date);
}

public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {

public boolean isValidRecurringDate(final LocalDate compareDate, final Boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(this.getRecurrence(), this.getStartDate(), compareDate, isSkipMeetingOnFirstDay,
return CalendarUtils.isValidRecurringDate(this.getRecurrence(), this.getStartDate(), compareDate, isSkipMeetingOnFirstDay,
numberOfDays);
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,14 +558,14 @@ private static String constructRecurrence(final CalendarFrequencyType frequencyT
public boolean isValidRecurringDate(final LocalDate compareDate, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {

if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth,
return CalendarUtils.isValidRecurringDate(getRecurrence(), getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth,
numberOfDays);
}

// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(history.getRecurrence(), history.getStartDate(), compareDate,
return CalendarUtils.isValidRecurringDate(history.getRecurrence(), history.getStartDate(), compareDate,
isSkipRepaymentOnFirstMonth, numberOfDays);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public static String getRRuleReadable(final LocalDate startDate, final String re
return humanReadable;
}

public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
public static boolean isValidRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return false;
Expand All @@ -365,7 +365,7 @@ public static boolean isValidRedurringDate(final String recurringRule, final Loc
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}

public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
public static boolean isValidRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final Integer numberOfDays) {

final Recur recur = CalendarUtils.getICalRecur(recurringRule);
Expand Down Expand Up @@ -529,7 +529,7 @@ public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, fi
}
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)
if (isValidRecurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)
&& !frequency.equals(Recur.Frequency.DAILY.name())) {
startDate = startDate.plusDays(1);
}
Expand Down Expand Up @@ -728,10 +728,6 @@ public static LocalDate getNextScheduleDate(final Calendar calendar, final Local
return null;
}
final LocalDate seedDate = calendar.getStartDateLocalDate();
/**
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
**/

return getNextRecurringDate(recur, seedDate, startDate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,21 @@ public void loanStatus(String statusExpected) throws IOException {
.isEqualTo(loanStatusExpectedValue);
}

@Then("Loan closedon_date is {}")
public void loanClosedonDate(String date) throws IOException {
Response<PostLoansResponse> loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
long loanId = loanCreateResponse.body().getLoanId();

Response<GetLoansLoanIdResponse> loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "", "", "").execute();
ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
testContext().set(TestContextKey.LOAN_RESPONSE, loanDetailsResponse);
if (date == null || "null".equals(date)) {
assertThat(loanDetailsResponse.body().getTimeline().getClosedOnDate()).isNull();
} else {
assertThat(loanDetailsResponse.body().getTimeline().getClosedOnDate()).isEqualTo(date);
}
}

@Then("Admin can successfully set Fraud flag to the loan")
public void setFraud() throws IOException {
Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# under the License.
#

fineract-test.api.base-url=${BASE_URL:https://localhost:8443}
fineract-test.api.base-url=${BASE_URL:http://localhost:8443}
fineract-test.api.username=${TEST_USERNAME:mifos}
fineract-test.api.password=${TEST_PASSWORD:password}
fineract-test.api.tenant-id=${TEST_TENANT_ID:default}
Expand Down
14 changes: 14 additions & 0 deletions fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
Original file line number Diff line number Diff line change
Expand Up @@ -1244,6 +1244,20 @@ Feature: Loan
| actualMaturityDate | expectedMaturityDate |
| 01 July 2023 | 01 July 2023 |

Scenario: Verify that closed date is updated on repayment reversal
When Admin sets the business date to "01 June 2023"
When Admin creates a client with random data
When Admin creates a new default Loan with date: "01 June 2023"
And Admin successfully approves the loan on "01 June 2023" with "1000" amount and expected disbursement date on "01 June 2023"
When Admin successfully disburse the loan on "01 June 2023" with "1000" EUR transaction amount
Then Loan status will be "ACTIVE"
When Admin sets the business date to "20 June 2023"
And Customer makes "AUTOPAY" repayment on "20 June 2023" with 1000 EUR transaction amount
Then Loan status will be "CLOSED_OBLIGATIONS_MET"
When Admin sets the business date to "20 June 2023"
When Customer undo "1"th "Repayment" transaction made on "20 June 2023"
Then Loan status will be "ACTIVE"
Then Loan closedon_date is null

Scenario: As an admin I would like to delete a loan using external id
When Admin sets the business date to the actual date
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ public void transition(final LoanEvent loanEvent, final Loan loan) {
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
}

// set mandatory field states based on new status after the transition
switch (newStatus) {
case ACTIVE -> {
if (loan.getClosedOnDate() != null) {
loan.setClosedOnDate(null);
}
}
default -> {
}
}
}
}

Expand Down Expand Up @@ -132,6 +143,7 @@ private LoanStatus getNextStatus(LoanEvent loanEvent, Loan loan) {
if (anyOfAllowedWhenComingFrom(from, LoanStatus.CLOSED_OBLIGATIONS_MET, LoanStatus.CLOSED_WRITTEN_OFF,
LoanStatus.CLOSED_RESCHEDULE_OUTSTANDING_AMOUNT)) {
newState = activeTransition();
loan.setClosedOnDate(null);
}
break;
case LOAN_INITIATE_TRANSFER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final
this.repaymentPeriodFrequencyType);
} else {
LocalDate expectedStartDate = startDate;
if (!CalendarUtils.isValidRedurringDate(loanCalendar.getRecurrence(),
if (!CalendarUtils.isValidRecurringDate(loanCalendar.getRecurrence(),
loanCalendar.getStartDateLocalDate().minusMonths(getRepaymentEvery()), startDate)) {
expectedStartDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(),
startDate.minusMonths(getRepaymentEvery()), startDate.minusMonths(getRepaymentEvery()), getRepaymentEvery(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ private List<DisbursementData> fetchDisbursementData(final JsonObject command) {

private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(),
if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRecurringDate(calendar.getRecurrence(),
calendar.getStartDateLocalDate(), repaymentsStartingFromDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1784,7 +1784,6 @@ private void validateTransactionProcessingStrategy(final String transactionProce
}

public void checkForProductMixRestrictions(final JsonElement element) {

final List<Long> activeLoansLoanProductIds;
final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
Expand Down Expand Up @@ -1869,17 +1868,17 @@ private void validateSubmittedOnDate(final JsonElement element, LocalDate origin
Group group = groupRepository.findOneWithNotFoundDetection(groupId);

if (group != null && group.isActivatedAfter(submittedOnDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
final String errorMessage = "The date on which a loan is submitted cannot be earlier than group's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
submittedOnDate, group.getActivationDate());
}
}

if (DateUtils.isAfter(submittedOnDate, expectedDisbursementDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ expectedDisbursementDate;
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOnDate, expectedDisbursementDate);
}
if (DateUtils.isAfter(submittedOnDate, expectedDisbursementDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ expectedDisbursementDate;
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOnDate, expectedDisbursementDate);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ public void validateRepaymentDateWithMeetingDate(final LocalDate repaymentDate,
final Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && repaymentDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
if (!CalendarUtils.isValidRecurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
calendar.getTitle());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,11 +1942,8 @@ private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder ba

public void validateMinMaxConstraints(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final LoanProduct loanProduct) {

validatePrincipalMinMaxConstraint(element, loanProduct, baseDataValidator);

validateNumberOfRepaymentsMinMaxConstraint(element, loanProduct, baseDataValidator);

validateNominalInterestRatePerPeriodMinMaxConstraint(element, loanProduct, baseDataValidator);
}

Expand Down Expand Up @@ -2029,7 +2026,6 @@ public void validateMinMaxConstraints(final JsonElement element, final DataValid

private void validatePrincipalMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) {

boolean principalUpdated = false;
boolean minPrincipalUpdated = false;
boolean maxPrincipalUpdated = false;
Expand Down Expand Up @@ -2555,11 +2551,7 @@ public void validateRepaymentPeriodWithGraceSettings(final Integer numberOfRepay
}
}

private Integer defaultToZeroIfNull(final Integer value) {
Integer result = value;
if (value == null) {
result = 0;
}
return result;
private Integer defaultToZeroIfNull(Integer value) {
return value != null ? value : 0;
}
}

0 comments on commit d87defc

Please sign in to comment.