Skip to content

Commit 6526e10

Browse files
ruchiDadamsaghy
authored andcommitted
FINERACT-1992: Backdated delinquency pause
1 parent da2cdd8 commit 6526e10

4 files changed

Lines changed: 195 additions & 12 deletions

File tree

fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
4040
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
4141
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
42+
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
4243
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
4344
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappings;
4445
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
@@ -230,8 +231,12 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand
230231
savedDelinquencyList, businessDate);
231232

232233
parsedDelinquencyAction.setLoan(loan);
233-
234234
LoanDelinquencyAction saved = loanDelinquencyActionRepository.saveAndFlush(parsedDelinquencyAction);
235+
// if backdated pause recalculate delinquency data
236+
if (DateUtils.isBefore(parsedDelinquencyAction.getStartDate(), businessDate)
237+
&& DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
238+
recalculateLoanDelinquencyData(loan);
239+
}
235240
businessEventNotifierService.notifyPostBusinessEvent(new LoanAccountDelinquencyPauseChangedBusinessEvent(loan));
236241
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
237242
.withEntityId(saved.getId()) //
@@ -242,6 +247,21 @@ public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand
242247
.build();
243248
}
244249

250+
private void recalculateLoanDelinquencyData(Loan loan) {
251+
List<LoanDelinquencyAction> savedDelinquencyList = delinquencyReadPlatformService.retrieveLoanDelinquencyActions(loan.getId());
252+
List<LoanDelinquencyActionData> effectiveDelinquencyList = delinquencyEffectivePauseHelper
253+
.calculateEffectiveDelinquencyList(savedDelinquencyList);
254+
255+
CollectionData loanDelinquencyData = loanDelinquencyDomainService.getOverdueCollectionData(loan, effectiveDelinquencyList);
256+
LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(loan.getId(),
257+
loanDelinquencyData.getDelinquentDate(), loanDelinquencyData.getDelinquentDays(), loan);
258+
if (loanScheduleDelinquencyData.getOverdueDays() > 0) {
259+
applyDelinquencyTagToLoan(loanScheduleDelinquencyData, effectiveDelinquencyList);
260+
} else {
261+
removeDelinquencyTagToLoan(loan);
262+
}
263+
}
264+
245265
@Override
246266
public void removeDelinquencyTagToLoan(final Loan loan) {
247267
if (loan.isEnableInstallmentLevelDelinquency()) {

fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidator.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ public LoanDelinquencyAction validateAndParseUpdate(@NotNull final JsonCommand c
5656
validateLoanIsActive(loan);
5757
if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
5858
validateBothStartAndEndDatesAreProvided(parsedDelinquencyAction);
59-
validatePauseStartAndEndDate(parsedDelinquencyAction, businessDate);
59+
validatePauseStartAndEndDate(parsedDelinquencyAction);
60+
validatePauseStartDateNotBeforeDisbursementDate(parsedDelinquencyAction, loan.getDisbursementDate());
6061
validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList);
6162
} else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
6263
validateResumeStartDate(parsedDelinquencyAction, businessDate);
@@ -117,15 +118,18 @@ private void validateResumeStartDate(LoanDelinquencyAction parsedDelinquencyActi
117118
}
118119
}
119120

120-
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) {
121+
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction) {
121122
if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) {
122123
raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date",
123124
"Delinquency pause period must be at least one day");
124125
}
126+
}
125127

126-
if (businessDate.isAfter(parsedDelinquencyAction.getStartDate())) {
127-
raiseValidationError("loan-delinquency-action-invalid-start-date", "Start date of pause period must be in the future",
128-
START_DATE);
128+
private void validatePauseStartDateNotBeforeDisbursementDate(LoanDelinquencyAction parsedDelinquencyAction,
129+
LocalDate firstDisbursalDate) {
130+
if (firstDisbursalDate.isAfter(parsedDelinquencyAction.getStartDate())) {
131+
raiseValidationError("loan-delinquency-action-invalid-start-date",
132+
"Start date of pause period must be after first disbursal date", START_DATE);
129133
}
130134
}
131135

fineract-provider/src/test/java/org/apache/fineract/portfolio/delinquency/validator/DelinquencyActionParseAndValidatorTest.java

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class DelinquencyActionParseAndValidatorTest {
6565
public void testParseAndValidationIsOKForPause() throws JsonProcessingException {
6666
Loan loan = Mockito.mock(Loan.class);
6767
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
68+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));
6869

6970
JsonCommand command = delinquencyAction("pause", "09 September 2022", "19 September 2022");
7071

@@ -96,6 +97,7 @@ public void testParseAndValidationIsOKForResume() throws JsonProcessingException
9697
public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws JsonProcessingException {
9798
Loan loan = Mockito.mock(Loan.class);
9899
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
100+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));
99101

100102
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
101103
JsonCommand command = delinquencyAction("pause", "09 September 2022", "15 September 2022");
@@ -111,6 +113,7 @@ public void testPauseBothStartAndEndDateIsOverlappingWithAnActivePause() throws
111113
public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessingException {
112114
Loan loan = Mockito.mock(Loan.class);
113115
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
116+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
114117

115118
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "14 September 2022", "22 September 2022"));
116119
JsonCommand command = delinquencyAction("pause", "15 September 2022", "23 September 2022");
@@ -126,7 +129,7 @@ public void testPauseStartIsOverlappingWithAnActivePause() throws JsonProcessing
126129
public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessingException {
127130
Loan loan = Mockito.mock(Loan.class);
128131
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
129-
132+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
130133
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
131134
JsonCommand command = delinquencyAction("pause", "13 September 2022", "20 September 2022");
132135
List<LoanDelinquencyActionData> effectiveList = List.of(loanDelinquencyActionData(existing.get(0)));
@@ -141,6 +144,7 @@ public void testNewPauseEndIsOverlappingWithExistingPause() throws JsonProcessin
141144
public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws JsonProcessingException {
142145
Loan loan = Mockito.mock(Loan.class);
143146
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
147+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
144148

145149
List<LoanDelinquencyAction> existing = List.of(loanDelinquencyAction(PAUSE, "15 September 2022", "22 September 2022"));
146150
JsonCommand command = delinquencyAction("pause", "15 September 2022", "22 September 2022");
@@ -156,6 +160,7 @@ public void testNewPauseIsOverlappingWithExistingPauseBecauseSameDates() throws
156160
public void testNewPauseIsNotOverlappingBecauseThereWasAResume() throws JsonProcessingException {
157161
Loan loan = Mockito.mock(Loan.class);
158162
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
163+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
159164

160165
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
161166

@@ -269,13 +274,15 @@ public void testValidationErrorPausePeriodShouldBeAtLeastOneDay() throws JsonPro
269274
}
270275

271276
@Test
272-
public void testValidationErrorPausePeriodMustBeInFuture() throws JsonProcessingException {
277+
public void testValidationErrorPausePeriodMustNotBeBeforeDisbursement() throws JsonProcessingException {
273278
Loan loan = Mockito.mock(Loan.class);
274279
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
280+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
275281

276282
JsonCommand command = delinquencyAction("pause", "08 September 2022", "09 September 2022");
277283

278-
assertPlatformValidationException("Start date of pause period must be in the future", "loan-delinquency-action-invalid-start-date",
284+
assertPlatformValidationException("Start date of pause period must be after first disbursal date",
285+
"loan-delinquency-action-invalid-start-date",
279286
() -> underTest.validateAndParseUpdate(command, loan, List.of(), localDate("09 September 2022")));
280287
}
281288

@@ -308,6 +315,7 @@ public void testStartDateIsMissingForResume() {
308315
public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingException {
309316
Loan loan = Mockito.mock(Loan.class);
310317
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
318+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
311319

312320
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
313321

@@ -324,6 +332,7 @@ public void testNewPausePeriodStartingOnExistingEndDate() throws JsonProcessingE
324332
public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingException {
325333
Loan loan = Mockito.mock(Loan.class);
326334
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
335+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
327336

328337
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
329338

@@ -340,6 +349,7 @@ public void testNewPauseEndingOnExistingStartDate() throws JsonProcessingExcepti
340349
public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonProcessingException {
341350
Loan loan = Mockito.mock(Loan.class);
342351
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
352+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("11 September 2022"));
343353

344354
JsonCommand command = delinquencyAction("pause", "18 September 2022", "20 September 2022");
345355

@@ -355,6 +365,21 @@ public void testNewPausePeriodStartingOnExistingEffectiveEndDate() throws JsonPr
355365
Assertions.assertEquals(localDate("20 September 2022"), parsedDelinquencyAction.getEndDate());
356366
}
357367

368+
@Test
369+
public void testParseAndValidationIsOKForBackdatedPause() throws JsonProcessingException {
370+
Loan loan = Mockito.mock(Loan.class);
371+
Mockito.when(loan.getStatus()).thenReturn(LoanStatus.ACTIVE);
372+
Mockito.when(loan.getDisbursementDate()).thenReturn(localDate("07 September 2022"));
373+
374+
JsonCommand command = delinquencyAction("pause", "08 September 2022", "19 September 2022");
375+
376+
LoanDelinquencyAction parsedDelinquencyAction = underTest.validateAndParseUpdate(command, loan, List.of(),
377+
localDate("09 September 2022"));
378+
Assertions.assertEquals(PAUSE, parsedDelinquencyAction.getAction());
379+
Assertions.assertEquals(localDate("08 September 2022"), parsedDelinquencyAction.getStartDate());
380+
Assertions.assertEquals(localDate("19 September 2022"), parsedDelinquencyAction.getEndDate());
381+
}
382+
358383
@NotNull
359384
private JsonCommand delinquencyAction(@Nullable String action, @Nullable String startDate, @Nullable String endDate)
360385
throws JsonProcessingException {

0 commit comments

Comments
 (0)