FINERACT-1992 Delinquency Pause API Changes
This commit is contained in:
+52
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.delinquency.api;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public final class DelinquencyApiResourceSwagger {
|
||||
|
||||
@@ -150,4 +151,55 @@ public final class DelinquencyApiResourceSwagger {
|
||||
public LocalDate liftedOnDate;
|
||||
}
|
||||
|
||||
@Schema(description = "GetDelinquencyActionsResponse")
|
||||
public static final class GetDelinquencyActionsResponse {
|
||||
|
||||
private GetDelinquencyActionsResponse() {}
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long id;
|
||||
@Schema(example = "pause")
|
||||
public String action;
|
||||
@Schema(example = "2013,1,2")
|
||||
public LocalDate startDate;
|
||||
@Schema(example = "2013,2,20")
|
||||
public LocalDate endDate;
|
||||
@Schema(example = "1")
|
||||
public Long createdById;
|
||||
@Schema(example = "1359463135000")
|
||||
public OffsetDateTime createdOn;
|
||||
@Schema(example = "1")
|
||||
public Long updatedById;
|
||||
@Schema(example = "1359463135000")
|
||||
public OffsetDateTime lastModifiedOn;
|
||||
}
|
||||
|
||||
@Schema(description = "PostLoansDelinquencyActionRequest")
|
||||
public static final class PostLoansDelinquencyActionRequest {
|
||||
|
||||
@Schema(example = "pause")
|
||||
public String action;
|
||||
@Schema(example = "2013-01-02")
|
||||
public String startDate;
|
||||
@Schema(example = "2013-02-20")
|
||||
public String endDate;
|
||||
@Schema(example = "yyyy-MM-dd")
|
||||
public String dateFormat;
|
||||
@Schema(example = "en")
|
||||
public String locale;
|
||||
}
|
||||
|
||||
@Schema(description = "PostLoansDelinquencyActionResponse")
|
||||
public static final class PostLoansDelinquencyActionResponse {
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long officeId;
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long clientId;
|
||||
|
||||
@Schema(example = "1")
|
||||
public Long resourceId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+5
@@ -19,6 +19,8 @@
|
||||
package org.apache.fineract.portfolio.delinquency.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
@@ -35,4 +37,7 @@ public interface LoanDelinquencyActionRepository
|
||||
+ " da.loan.id = :loan_id order by da.createdDate desc")
|
||||
Page<LoanDelinquencyAction> getEffectiveDelinquencyActionForLoan(@Param("loan_id") Long loan_id,
|
||||
@Param("business_date") LocalDate business_date, Pageable page);
|
||||
|
||||
List<LoanDelinquencyAction> findByLoanOrderById(Loan loan);
|
||||
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.handler;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.commands.annotation.CommandType;
|
||||
import org.apache.fineract.commands.handler.NewCommandSourceHandler;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@CommandType(entity = "DELINQUENCY_ACTION", action = "CREATE")
|
||||
public class CreateDelinquencyActionCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final DelinquencyWritePlatformService writePlatformService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.createDelinquencyAction(command.getLoanId(), command);
|
||||
}
|
||||
}
|
||||
+3
@@ -23,6 +23,7 @@ import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
|
||||
public interface DelinquencyReadPlatformService {
|
||||
@@ -43,4 +44,6 @@ public interface DelinquencyReadPlatformService {
|
||||
|
||||
Collection<LoanInstallmentDelinquencyTagData> retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId);
|
||||
|
||||
Collection<LoanDelinquencyAction> retrieveLoanDelinquencyActions(Long loanId);
|
||||
|
||||
}
|
||||
|
||||
+71
@@ -18,24 +18,37 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.service;
|
||||
|
||||
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanInstallmentDelinquencyTagData;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
|
||||
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyRangeMapper;
|
||||
import org.apache.fineract.portfolio.delinquency.mapper.LoanDelinquencyTagMapper;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
@@ -55,6 +68,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
private final LoanRepository loanRepository;
|
||||
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
|
||||
private final LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag;
|
||||
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
|
||||
|
||||
@Override
|
||||
public Collection<DelinquencyRangeData> retrieveAllDelinquencyRanges() {
|
||||
@@ -124,14 +138,71 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
collectionData.setLastRepaymentDate(lastRepaymentTransaction.getTransactionDate());
|
||||
collectionData.setLastRepaymentAmount(lastRepaymentTransaction.getAmount());
|
||||
}
|
||||
|
||||
enrichWithDelinquencyPausePeriodInfo(collectionData, retrieveLoanDelinquencyActions(loanId),
|
||||
ThreadLocalContextUtil.getBusinessDate());
|
||||
}
|
||||
|
||||
return collectionData;
|
||||
}
|
||||
|
||||
void enrichWithDelinquencyPausePeriodInfo(CollectionData collectionData, Collection<LoanDelinquencyAction> delinquencyActions,
|
||||
LocalDate businessDate) {
|
||||
// partition them based on type
|
||||
Map<DelinquencyAction, List<LoanDelinquencyAction>> partitioned = delinquencyActions.stream()
|
||||
.collect(Collectors.groupingBy(LoanDelinquencyAction::getAction));
|
||||
|
||||
// add the possible resumes to it to create the effective pause periods
|
||||
if (partitioned.containsKey(DelinquencyAction.PAUSE)) {
|
||||
List<LoanDelinquencyActionData> effective = new ArrayList<>();
|
||||
List<LoanDelinquencyAction> pauses = partitioned.get(DelinquencyAction.PAUSE);
|
||||
for (LoanDelinquencyAction loanDelinquencyAction : pauses) {
|
||||
Optional<LoanDelinquencyAction> resume = findMatchingResume(loanDelinquencyAction, partitioned.get(RESUME));
|
||||
LoanDelinquencyActionData loanDelinquencyActionData = new LoanDelinquencyActionData(loanDelinquencyAction);
|
||||
resume.ifPresent(r -> loanDelinquencyActionData.setEndDate(r.getStartDate()));
|
||||
effective.add(loanDelinquencyActionData);
|
||||
}
|
||||
|
||||
// order them by start date, filter out the future items
|
||||
Optional<LoanDelinquencyActionData> last = effective.stream()
|
||||
.sorted(Comparator.comparing(LoanDelinquencyActionData::getStartDate))
|
||||
.filter(e -> !DateUtils.isAfter(e.getStartDate(), businessDate)) // keep only the present and the
|
||||
// past ones
|
||||
.reduce((first, second) -> second);
|
||||
|
||||
// enrich collectionData
|
||||
last.ifPresent(action -> {
|
||||
collectionData
|
||||
.setDelinquencyCalculationPaused(!action.startDate.isAfter(businessDate) && !businessDate.isAfter(action.endDate));
|
||||
collectionData.setDelinquencyPausePeriodStartDate(action.getStartDate());
|
||||
collectionData.setDelinquencyPausePeriodEndDate(action.getEndDate());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<LoanDelinquencyAction> findMatchingResume(LoanDelinquencyAction pause, List<LoanDelinquencyAction> resumes) {
|
||||
if (resumes != null && resumes.size() > 0) {
|
||||
for (LoanDelinquencyAction resume : resumes) {
|
||||
if (!pause.getStartDate().isAfter(resume.getStartDate()) && !resume.getStartDate().isAfter(pause.getEndDate())) {
|
||||
return Optional.of(resume);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoanInstallmentDelinquencyTagData> retrieveLoanInstallmentsCurrentDelinquencyTag(Long loanId) {
|
||||
return repositoryLoanInstallmentDelinquencyTag.findInstallmentDelinquencyTags(loanId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoanDelinquencyAction> retrieveLoanDelinquencyActions(Long loanId) {
|
||||
final Optional<Loan> optLoan = this.loanRepository.findById(loanId);
|
||||
if (optLoan.isPresent()) {
|
||||
return loanDelinquencyActionRepository.findByLoanOrderById(optLoan.get());
|
||||
}
|
||||
return List.of();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -47,4 +47,6 @@ public interface DelinquencyWritePlatformService {
|
||||
|
||||
void applyDelinquencyTagToLoan(LoanScheduleDelinquencyData loanDelinquencyData);
|
||||
|
||||
CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand command);
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -44,12 +44,15 @@ import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMapping
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTag;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketAgesOverlapedException;
|
||||
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInvalidAgesException;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.CollectionData;
|
||||
@@ -76,6 +79,8 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanDelinquencyDomainService loanDelinquencyDomainService;
|
||||
private final LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository;
|
||||
private final LoanDelinquencyActionRepository loanDelinquencyActionRepository;
|
||||
private final DelinquencyActionParseAndValidator delinquencyActionParseAndValidator;
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
|
||||
@@ -199,6 +204,28 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult createDelinquencyAction(Long loanId, JsonCommand command) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
final List<LoanDelinquencyAction> savedDelinquencyList = loanDelinquencyActionRepository.findByLoanOrderById(loan);
|
||||
|
||||
LoanDelinquencyAction parsedDelinquencyAction = delinquencyActionParseAndValidator.validateAndParseUpdate(command, loan,
|
||||
savedDelinquencyList, businessDate);
|
||||
|
||||
parsedDelinquencyAction.setLoan(loan);
|
||||
|
||||
LoanDelinquencyAction saved = loanDelinquencyActionRepository.saveAndFlush(parsedDelinquencyAction);
|
||||
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()) //
|
||||
.withEntityId(saved.getId()) //
|
||||
.withOfficeId(loan.getOfficeId()) //
|
||||
.withClientId(loan.getClientId()) //
|
||||
.withGroupId(loan.getGroupId()) //
|
||||
.withLoanId(loanId) //
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDelinquencyTagToLoan(final Loan loan) {
|
||||
setLoanDelinquencyTag(loan, null);
|
||||
|
||||
+10
-4
@@ -22,6 +22,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyActionRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanInstallmentDelinquencyTagRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.mapper.DelinquencyBucketMapper;
|
||||
@@ -33,6 +34,7 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatfor
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
|
||||
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainService;
|
||||
import org.apache.fineract.portfolio.delinquency.service.LoanDelinquencyDomainServiceImpl;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyActionParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
|
||||
@@ -52,10 +54,11 @@ public class DelinquencyConfiguration {
|
||||
DelinquencyRangeMapper mapperRange, DelinquencyBucketMapper mapperBucket,
|
||||
LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory, LoanRepository loanRepository,
|
||||
LoanDelinquencyDomainService loanDelinquencyDomainService,
|
||||
LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag) {
|
||||
LoanInstallmentDelinquencyTagRepository repositoryLoanInstallmentDelinquencyTag,
|
||||
LoanDelinquencyActionRepository loanDelinquencyActionRepository) {
|
||||
return new DelinquencyReadPlatformServiceImpl(repositoryRange, repositoryBucket, repositoryLoanDelinquencyTagHistory, mapperRange,
|
||||
mapperBucket, mapperLoanDelinquencyTagHistory, loanRepository, loanDelinquencyDomainService,
|
||||
repositoryLoanInstallmentDelinquencyTag);
|
||||
repositoryLoanInstallmentDelinquencyTag, loanDelinquencyActionRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -66,10 +69,13 @@ public class DelinquencyConfiguration {
|
||||
LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository, LoanRepositoryWrapper loanRepository,
|
||||
LoanProductRepository loanProductRepository, BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanDelinquencyDomainService loanDelinquencyDomainService,
|
||||
LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository) {
|
||||
LoanInstallmentDelinquencyTagRepository loanInstallmentDelinquencyTagRepository,
|
||||
LoanDelinquencyActionRepository loanDelinquencyActionRepository,
|
||||
DelinquencyActionParseAndValidator delinquencyActionParseAndValidator) {
|
||||
return new DelinquencyWritePlatformServiceImpl(dataValidatorBucket, dataValidatorRange, repositoryRange, repositoryBucket,
|
||||
repositoryBucketMappings, loanDelinquencyTagRepository, loanRepository, loanProductRepository, businessEventNotifierService,
|
||||
loanDelinquencyDomainService, loanInstallmentDelinquencyTagRepository);
|
||||
loanDelinquencyDomainService, loanInstallmentDelinquencyTagRepository, loanDelinquencyActionRepository,
|
||||
delinquencyActionParseAndValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.validator;
|
||||
|
||||
public final class DelinquencyActionParameters {
|
||||
|
||||
private DelinquencyActionParameters() {}
|
||||
|
||||
public static final String ACTION = "action";
|
||||
public static final String START_DATE = "startDate";
|
||||
public static final String END_DATE = "endDate";
|
||||
public static final String DATE_FORMAT = "dateFormat";
|
||||
|
||||
public static final String LOCALE = "locale";
|
||||
}
|
||||
+199
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.validator;
|
||||
|
||||
import static org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction.RESUME;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.validator.ParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Component
|
||||
public class DelinquencyActionParseAndValidator extends ParseAndValidator {
|
||||
|
||||
private final FromJsonHelper jsonHelper;
|
||||
|
||||
public LoanDelinquencyAction validateAndParseUpdate(@NotNull final JsonCommand command, Loan loan,
|
||||
List<LoanDelinquencyAction> savedDelinquencyActions, LocalDate businessDate) {
|
||||
List<LoanDelinquencyActionData> effectiveDelinquencyList = calculateEffectiveDelinquencyList(savedDelinquencyActions);
|
||||
LoanDelinquencyAction parsedDelinquencyAction = parseCommand(command);
|
||||
validateLoanIsActive(loan);
|
||||
if (DelinquencyAction.PAUSE.equals(parsedDelinquencyAction.getAction())) {
|
||||
validatePauseStartAndEndDate(parsedDelinquencyAction, businessDate);
|
||||
validatePauseShallNotOverlap(parsedDelinquencyAction, effectiveDelinquencyList);
|
||||
} else if (DelinquencyAction.RESUME.equals(parsedDelinquencyAction.getAction())) {
|
||||
validateResumeStartDate(parsedDelinquencyAction, businessDate);
|
||||
validateResumeNoEndDate(parsedDelinquencyAction);
|
||||
validateResumeShouldBeOnActivePause(parsedDelinquencyAction, effectiveDelinquencyList);
|
||||
}
|
||||
return parsedDelinquencyAction;
|
||||
}
|
||||
|
||||
private List<LoanDelinquencyActionData> calculateEffectiveDelinquencyList(List<LoanDelinquencyAction> savedDelinquencyActions) {
|
||||
// partition them based on type
|
||||
Map<DelinquencyAction, List<LoanDelinquencyAction>> partitioned = savedDelinquencyActions.stream()
|
||||
.collect(Collectors.groupingBy(LoanDelinquencyAction::getAction));
|
||||
List<LoanDelinquencyActionData> effective = new ArrayList<>();
|
||||
List<LoanDelinquencyAction> pauses = partitioned.get(DelinquencyAction.PAUSE);
|
||||
if (pauses != null && pauses.size() > 0) {
|
||||
for (LoanDelinquencyAction loanDelinquencyAction : pauses) {
|
||||
Optional<LoanDelinquencyAction> resume = findMatchingResume(loanDelinquencyAction, partitioned.get(RESUME));
|
||||
LoanDelinquencyActionData loanDelinquencyActionData = new LoanDelinquencyActionData(loanDelinquencyAction);
|
||||
resume.ifPresent(r -> loanDelinquencyActionData.setEndDate(r.getStartDate()));
|
||||
effective.add(loanDelinquencyActionData);
|
||||
}
|
||||
}
|
||||
return effective;
|
||||
}
|
||||
|
||||
private Optional<LoanDelinquencyAction> findMatchingResume(LoanDelinquencyAction pause, List<LoanDelinquencyAction> resumes) {
|
||||
if (resumes != null && resumes.size() > 0) {
|
||||
for (LoanDelinquencyAction resume : resumes) {
|
||||
if (!pause.getStartDate().isAfter(resume.getStartDate()) && !resume.getStartDate().isAfter(pause.getEndDate())) {
|
||||
return Optional.of(resume);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void validateResumeShouldBeOnActivePause(LoanDelinquencyAction parsedDelinquencyAction,
|
||||
List<LoanDelinquencyActionData> savedDelinquencyActions) {
|
||||
boolean match = savedDelinquencyActions.stream()
|
||||
.anyMatch(lda -> !DateUtils.isBefore(parsedDelinquencyAction.getStartDate(), lda.getStartDate())
|
||||
&& !DateUtils.isAfter(parsedDelinquencyAction.getStartDate(), lda.getEndDate()));
|
||||
if (!match) {
|
||||
raiseValidationError("loan-delinquency-action-resume-should-be-on-pause",
|
||||
"Resume Delinquency Action can only be created during an active pause");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateResumeNoEndDate(LoanDelinquencyAction parsedDelinquencyAction) {
|
||||
if (parsedDelinquencyAction.getEndDate() != null) {
|
||||
raiseValidationError("loan-delinquency-action-resume-should-have-no-end-date",
|
||||
"Resume Delinquency action can not have end date");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateResumeStartDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) {
|
||||
if (!parsedDelinquencyAction.getStartDate().equals(businessDate)) {
|
||||
raiseValidationError("loan-delinquency-action-invalid-start-date",
|
||||
"Start date of the Resume Delinquency action must be the current business date");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePauseStartAndEndDate(LoanDelinquencyAction parsedDelinquencyAction, LocalDate businessDate) {
|
||||
if (parsedDelinquencyAction.getStartDate().equals(parsedDelinquencyAction.getEndDate())) {
|
||||
raiseValidationError("loan-delinquency-action-invalid-start-date-and-end-date",
|
||||
"Delinquency pause period must be at least one day");
|
||||
}
|
||||
|
||||
if (businessDate.isAfter(parsedDelinquencyAction.getStartDate())) {
|
||||
raiseValidationError("loan-delinquency-action-invalid-start-date", "Start date of pause period must be in the future");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateLoanIsActive(Loan loan) {
|
||||
if (!loan.getStatus().isActive()) {
|
||||
raiseValidationError("loan-delinquency-action-invalid-loan-state", "Delinquency actions can be created only for active loans.");
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePauseShallNotOverlap(LoanDelinquencyAction parsedDelinquencyAction,
|
||||
List<LoanDelinquencyActionData> delinquencyActions) {
|
||||
if (delinquencyActions.stream().filter(lda -> lda.getAction().equals(DelinquencyAction.PAUSE))
|
||||
.anyMatch(lda -> isOverlapping(parsedDelinquencyAction, lda))) {
|
||||
raiseValidationError("loan-delinquency-action-overlapping",
|
||||
"Delinquency pause period cannot overlap with another pause period");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOverlapping(LoanDelinquencyAction parsedDelinquencyAction, LoanDelinquencyActionData ldad) {
|
||||
return ((!parsedDelinquencyAction.getStartDate().isAfter(ldad.getStartDate())
|
||||
&& !ldad.getStartDate().isAfter(parsedDelinquencyAction.getEndDate()))
|
||||
|| (!parsedDelinquencyAction.getStartDate().isAfter(ldad.getEndDate())
|
||||
&& !ldad.getEndDate().isAfter(parsedDelinquencyAction.getEndDate())));
|
||||
}
|
||||
|
||||
@org.jetbrains.annotations.NotNull
|
||||
private LoanDelinquencyAction parseCommand(@org.jetbrains.annotations.NotNull JsonCommand command) {
|
||||
LoanDelinquencyAction parsedDelinquencyAction = new LoanDelinquencyAction();
|
||||
parsedDelinquencyAction.setAction(extractAction(command.parsedJson()));
|
||||
parsedDelinquencyAction.setStartDate(extractStartDate(command.parsedJson()));
|
||||
parsedDelinquencyAction.setEndDate(extractEndDate(command.parsedJson()));
|
||||
return parsedDelinquencyAction;
|
||||
}
|
||||
|
||||
private DelinquencyAction extractAction(JsonElement json) {
|
||||
String actionString = jsonHelper.extractStringNamed(DelinquencyActionParameters.ACTION, json);
|
||||
validateActionString(actionString);
|
||||
if ("pause".equalsIgnoreCase(actionString)) {
|
||||
return DelinquencyAction.PAUSE;
|
||||
} else if ("resume".equalsIgnoreCase(actionString)) {
|
||||
return DelinquencyAction.RESUME;
|
||||
} else {
|
||||
throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError("loan-delinquency-action-invalid-action",
|
||||
"Invalid Delinquency Action: " + actionString)));
|
||||
}
|
||||
}
|
||||
|
||||
private void validateActionString(String actionString) {
|
||||
if (StringUtils.isEmpty(actionString)) {
|
||||
raiseValidationError("loan-delinquency-action-missing-action", "Delinquency Action must not be null or empty");
|
||||
}
|
||||
}
|
||||
|
||||
private LocalDate extractStartDate(JsonElement json) {
|
||||
String dateFormat = jsonHelper.extractStringNamed(DelinquencyActionParameters.DATE_FORMAT, json);
|
||||
String locale = jsonHelper.extractStringNamed(DelinquencyActionParameters.LOCALE, json);
|
||||
return jsonHelper.extractLocalDateNamed(DelinquencyActionParameters.START_DATE, json, dateFormat,
|
||||
JsonParserHelper.localeFromString(locale));
|
||||
}
|
||||
|
||||
private LocalDate extractEndDate(JsonElement json) {
|
||||
String dateFormat = jsonHelper.extractStringNamed(DelinquencyActionParameters.DATE_FORMAT, json);
|
||||
String locale = jsonHelper.extractStringNamed(DelinquencyActionParameters.LOCALE, json);
|
||||
return jsonHelper.extractLocalDateNamed(DelinquencyActionParameters.END_DATE, json, dateFormat,
|
||||
JsonParserHelper.localeFromString(locale));
|
||||
}
|
||||
|
||||
private void raiseValidationError(String globalisationMessageCode, String msg) throws PlatformApiDataValidationException {
|
||||
throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError(globalisationMessageCode, msg)));
|
||||
}
|
||||
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.validator;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import lombok.Data;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
|
||||
@Data
|
||||
public class LoanDelinquencyActionData {
|
||||
|
||||
public Long id;
|
||||
public DelinquencyAction action;
|
||||
public LocalDate startDate;
|
||||
public LocalDate endDate;
|
||||
public Long createdById;
|
||||
public OffsetDateTime createdOn;
|
||||
public Long updatedById;
|
||||
public OffsetDateTime lastModifiedOn;
|
||||
|
||||
public LoanDelinquencyActionData(LoanDelinquencyAction loanDelinquencyAction) {
|
||||
this.id = loanDelinquencyAction.getId();
|
||||
this.action = loanDelinquencyAction.getAction();
|
||||
this.startDate = loanDelinquencyAction.getStartDate();
|
||||
this.endDate = loanDelinquencyAction.getEndDate();
|
||||
|
||||
loanDelinquencyAction.getCreatedBy().ifPresent(this::setCreatedById);
|
||||
loanDelinquencyAction.getLastModifiedBy().ifPresent(this::setUpdatedById);
|
||||
this.createdOn = loanDelinquencyAction.getCreatedDateTime();
|
||||
this.lastModifiedOn = loanDelinquencyAction.getLastModifiedDateTime();
|
||||
}
|
||||
|
||||
}
|
||||
+77
@@ -109,7 +109,9 @@ import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateral
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiResourceSwagger;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyAction;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.LoanDelinquencyActionData;
|
||||
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
|
||||
import org.apache.fineract.portfolio.fund.data.FundData;
|
||||
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
|
||||
@@ -257,6 +259,7 @@ public class LoansApiResource {
|
||||
private final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer;
|
||||
private final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer;
|
||||
private final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer;
|
||||
private final DefaultToApiJsonSerializer<LoanDelinquencyActionData> delinquencyActionSerializer;
|
||||
private final ApiRequestParameterHelper apiRequestParameterHelper;
|
||||
private final FromJsonHelper fromJsonHelper;
|
||||
private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService;
|
||||
@@ -778,6 +781,58 @@ public class LoansApiResource {
|
||||
return getDelinquencyTagHistory(null, loanExternalId, uriInfo);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{loanId}/delinquency-actions")
|
||||
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Retrieve delinquency actions related to the loan", description = "")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyActionsResponse.class)))) })
|
||||
public String getLoanDelinquencyActions(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
|
||||
@Context final UriInfo uriInfo) {
|
||||
return getLoanDelinquencyActions(loanId, null, uriInfo);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("external-id/{loanExternalId}/delinquency-actions")
|
||||
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Retrieve delinquency actions related to the loan", description = "")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyActionsResponse.class)))) })
|
||||
public String getLoanDelinquencyActions(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
|
||||
@Context final UriInfo uriInfo) {
|
||||
return getLoanDelinquencyActions(null, loanExternalId, uriInfo);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("{loanId}/delinquency-actions")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Adds a new delinquency action for a loan", description = "")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionRequest.class)))
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionResponse.class))) })
|
||||
public String createLoanDelinquencyAction(@PathParam("loanId") @Parameter(description = "loanId", required = true) final Long loanId,
|
||||
@Context final UriInfo uriInfo, @Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
return createLoanDelinquencyAction(loanId, ExternalId.empty(), apiRequestBodyAsJson);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("external-id/{loanExternalId}/delinquency-actions")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Adds a new delinquency action for a loan", description = "")
|
||||
@RequestBody(required = true, content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionRequest.class)))
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.PostLoansDelinquencyActionResponse.class))) })
|
||||
public String createLoanDelinquencyAction(
|
||||
@PathParam("loanExternalId") @Parameter(description = "loanExternalId", required = true) final String loanExternalId,
|
||||
@Context final UriInfo uriInfo, @Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
return createLoanDelinquencyAction(null, ExternalIdFactory.produce(loanExternalId), apiRequestBodyAsJson);
|
||||
}
|
||||
|
||||
private String retrieveApprovalTemplate(final Long loanId, final String loanExternalIdStr, final String templateType,
|
||||
final UriInfo uriInfo) {
|
||||
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
@@ -1154,4 +1209,26 @@ public class LoansApiResource {
|
||||
}
|
||||
return resolvedLoanId;
|
||||
}
|
||||
|
||||
private String getLoanDelinquencyActions(final Long loanId, final String loanExternalIdStr, final UriInfo uriInfo) {
|
||||
context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
ExternalId loanExternalId = ExternalIdFactory.produce(loanExternalIdStr);
|
||||
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
|
||||
|
||||
final Collection<LoanDelinquencyAction> delinquencyActions = this.delinquencyReadPlatformService
|
||||
.retrieveLoanDelinquencyActions(resolvedLoanId);
|
||||
List<LoanDelinquencyActionData> result = delinquencyActions.stream().map(LoanDelinquencyActionData::new).toList();
|
||||
return this.jsonSerializerTagHistory.serialize(result);
|
||||
}
|
||||
|
||||
private String createLoanDelinquencyAction(Long loanId, ExternalId loanExternalId, String apiRequestBodyAsJson) {
|
||||
context.authenticatedUser().validateHasUpdatePermission(RESOURCE_NAME_FOR_PERMISSIONS);
|
||||
Long resolvedLoanId = getResolvedLoanId(loanId, loanExternalId);
|
||||
|
||||
CommandWrapperBuilder builder = new CommandWrapperBuilder().createDelinquencyAction(resolvedLoanId);
|
||||
builder.withJson(apiRequestBodyAsJson);
|
||||
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(builder.build());
|
||||
return delinquencyActionSerializer.serialize(result);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+13
-3
@@ -964,9 +964,9 @@ final class LoansApiResourceSwagger {
|
||||
private String externalId;
|
||||
}
|
||||
|
||||
static final class GetLoansLoanIdCollectionData {
|
||||
static final class GetLoansLoanIdDelinquencySummary {
|
||||
|
||||
private GetLoansLoanIdCollectionData() {}
|
||||
private GetLoansLoanIdDelinquencySummary() {}
|
||||
|
||||
@Schema(example = "100.000000")
|
||||
public Double availableDisbursementAmount;
|
||||
@@ -989,6 +989,16 @@ final class LoansApiResourceSwagger {
|
||||
public LocalDate lastRepaymentDate;
|
||||
@Schema(example = "100.000000")
|
||||
public Double lastRepaymentAmount;
|
||||
|
||||
@Schema(example = "true")
|
||||
public Boolean delinquencyCalculationPaused;
|
||||
|
||||
@Schema(example = "[2022, 07, 05]")
|
||||
public LocalDate delinquencyPausePeriodStartDate;
|
||||
|
||||
@Schema(example = "[2022, 07, 10]")
|
||||
public LocalDate delinquencyPausePeriodEndDate;
|
||||
|
||||
}
|
||||
|
||||
@Schema(example = "1")
|
||||
@@ -1062,7 +1072,7 @@ final class LoansApiResourceSwagger {
|
||||
@Schema(description = "Set of GetLoansLoanIdDisbursementDetails")
|
||||
public Set<GetLoansLoanIdDisbursementDetails> disbursementDetails;
|
||||
@Schema(description = "Delinquent data")
|
||||
public GetLoansLoanIdCollectionData delinquent;
|
||||
public GetLoansLoanIdDelinquencySummary delinquent;
|
||||
@Schema(description = "Set of charges")
|
||||
public List<GetLoansLoanIdLoanChargeData> charges;
|
||||
public GetDelinquencyRangesResponse delinquencyRange;
|
||||
|
||||
+5
-1
@@ -43,9 +43,13 @@ public final class CollectionData {
|
||||
private LocalDate lastRepaymentDate;
|
||||
private BigDecimal lastRepaymentAmount;
|
||||
|
||||
public boolean delinquencyCalculationPaused;
|
||||
public LocalDate delinquencyPausePeriodStartDate;
|
||||
public LocalDate delinquencyPausePeriodEndDate;
|
||||
|
||||
public static CollectionData template() {
|
||||
final BigDecimal zero = BigDecimal.ZERO;
|
||||
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero);
|
||||
return new CollectionData(zero, 0L, null, 0L, null, zero, null, zero, null, zero, false, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user