Loan Delinquency Classification job
This commit is contained in:
committed by
Arnold Gálovics
parent
0af87a8766
commit
8b3975f8d7
+9
@@ -1155,6 +1155,15 @@ public class CommandWrapperBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder assignDelinquency(final Long loanId) {
|
||||
this.actionName = "UPDATEDELINQUENCY";
|
||||
this.entityName = "LOAN";
|
||||
this.entityId = loanId;
|
||||
this.loanId = loanId;
|
||||
this.href = "/loans/" + loanId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CommandWrapperBuilder createCodeValue(final Long codeId) {
|
||||
this.actionName = "CREATE";
|
||||
this.entityName = "CODEVALUE";
|
||||
|
||||
+5
@@ -18,6 +18,8 @@
|
||||
*/
|
||||
package org.apache.fineract.infrastructure.core.service;
|
||||
|
||||
import static java.time.temporal.ChronoUnit.DAYS;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
@@ -76,4 +78,7 @@ public final class DateUtils {
|
||||
return ThreadLocalContextUtil.getBusinessDate();
|
||||
}
|
||||
|
||||
public static long getDifferenceInDays(final LocalDate localDateBefore, final LocalDate localDateAfter) {
|
||||
return DAYS.between(localDateBefore, localDateAfter);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -55,7 +55,8 @@ public enum JobName {
|
||||
EXECUTE_DIRTY_JOBS("Execute All Dirty Jobs"), //
|
||||
INCREASE_BUSINESS_DATE_BY_1_DAY("Increase Business Date by 1 day"), //
|
||||
INCREASE_COB_DATE_BY_1_DAY("Increase COB Date by 1 day"), //
|
||||
LOAN_COB("Loan COB");
|
||||
LOAN_COB("Loan COB"), //
|
||||
LOAN_DELINQUENCY_CLASSIFICATION("Loan Delinquency Classification");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
|
||||
@ToString
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public class LoanDelinquencyTagHistoryData implements Serializable {
|
||||
|
||||
private Long id;
|
||||
private Long loanId;
|
||||
private String delinquencyRange;
|
||||
private LocalDate addedOnDate;
|
||||
private LocalDate liftedOnDate;
|
||||
|
||||
}
|
||||
+2
-2
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.domain;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.List;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
@@ -46,7 +46,7 @@ public class DelinquencyBucket extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|
||||
@ManyToMany(fetch = FetchType.LAZY)
|
||||
@JoinTable(name = "m_delinquency_bucket_mappings", joinColumns = @JoinColumn(name = "delinquency_bucket_id"), inverseJoinColumns = @JoinColumn(name = "delinquency_range_id"))
|
||||
private Set<DelinquencyRange> ranges;
|
||||
private List<DelinquencyRange> ranges;
|
||||
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
+1
@@ -25,4 +25,5 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
public interface DelinquencyRangeRepository extends JpaRepository<DelinquencyRange, Long>, JpaSpecificationExecutor<DelinquencyRange> {
|
||||
|
||||
Optional<DelinquencyRange> findByClassification(String classification);
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -28,6 +28,8 @@ import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
public interface LoanDelinquencyTagHistoryRepository
|
||||
extends JpaRepository<LoanDelinquencyTagHistory, Long>, JpaSpecificationExecutor<LoanDelinquencyTagHistory> {
|
||||
|
||||
LoanDelinquencyTagHistory findFirstByLoanOrderByAddedOnDateDesc(Loan loan);
|
||||
|
||||
List<LoanDelinquencyTagHistory> findByLoanOrderByAddedOnDateDesc(Loan loan);
|
||||
|
||||
Optional<LoanDelinquencyTagHistory> findByLoanAndLiftedOnDate(Loan loan, LocalDate liftedOnDate);
|
||||
|
||||
+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 = "LOAN", action = "UPDATEDELINQUENCY")
|
||||
public class UpdateLoanDelinquencyBucketCommandHandler implements NewCommandSourceHandler {
|
||||
|
||||
private final DelinquencyWritePlatformService writePlatformService;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public CommandProcessingResult processCommand(final JsonCommand command) {
|
||||
return this.writePlatformService.applyDelinquencyTagToLoan(command.entityId(), command);
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 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.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistory;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Mappings;
|
||||
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface LoanDelinquencyTagMapper {
|
||||
|
||||
@Mappings({ @Mapping(target = "id", source = "source.id"), @Mapping(target = "loanId", source = "source.loan.id"),
|
||||
@Mapping(target = "delinquencyRange", source = "source.delinquencyRange.classification"),
|
||||
@Mapping(target = "addedOnDate", source = "source.addedOnDate"),
|
||||
@Mapping(target = "liftedOnDate", source = "source.liftedOnDate") })
|
||||
LoanDelinquencyTagHistoryData map(LoanDelinquencyTagHistory source);
|
||||
|
||||
List<LoanDelinquencyTagHistoryData> map(List<LoanDelinquencyTagHistory> sources);
|
||||
|
||||
}
|
||||
+4
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.delinquency.service;
|
||||
import java.util.Collection;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
|
||||
public interface DelinquencyReadPlatformService {
|
||||
|
||||
@@ -33,4 +34,7 @@ public interface DelinquencyReadPlatformService {
|
||||
DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId);
|
||||
|
||||
DelinquencyRangeData retrieveCurrentDelinquencyTag(Long loanId);
|
||||
|
||||
Collection<LoanDelinquencyTagHistoryData> retrieveDelinquencyRangeHistory(Long loanId);
|
||||
|
||||
}
|
||||
|
||||
+13
-36
@@ -18,28 +18,25 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.delinquency.service;
|
||||
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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.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.LoanDelinquencyTagHistory;
|
||||
import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
|
||||
import org.apache.fineract.portfolio.delinquency.exception.DelinquencyBucketNotFoundException;
|
||||
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.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -54,6 +51,7 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
private final LoanDelinquencyTagHistoryRepository repositoryLoanDelinquencyTagHistory;
|
||||
private final DelinquencyRangeMapper mapperRange;
|
||||
private final DelinquencyBucketMapper mapperBucket;
|
||||
private final LoanDelinquencyTagMapper mapperLoanDelinquencyTagHistory;
|
||||
private final LoanAssembler loanAssembler;
|
||||
|
||||
@Override
|
||||
@@ -76,19 +74,10 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
|
||||
@Override
|
||||
public DelinquencyBucketData retrieveDelinquencyBucket(Long delinquencyBucketId) {
|
||||
try {
|
||||
final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
|
||||
final DelinquencyBucketData delinquencyBucketData = mapperBucket.map(delinquencyBucket);
|
||||
|
||||
final DelinquencyBucketMappingMapper rmMappings = new DelinquencyBucketMappingMapper();
|
||||
final String sql = "select " + rmMappings.schema() + " where buc.id = ? order by dran.min_age_days";
|
||||
List<DelinquencyRangeData> ranges = this.jdbcTemplate.query(sql, rmMappings, delinquencyBucketId); // NOSONAR
|
||||
delinquencyBucketData.setRanges(ranges);
|
||||
|
||||
return delinquencyBucketData;
|
||||
} catch (final EmptyResultDataAccessException e) {
|
||||
throw DelinquencyBucketNotFoundException.notFound(delinquencyBucketId, e);
|
||||
}
|
||||
final DelinquencyBucket delinquencyBucket = repositoryBucket.getReferenceById(delinquencyBucketId);
|
||||
final DelinquencyBucketData delinquencyBucketData = mapperBucket.map(delinquencyBucket);
|
||||
delinquencyBucketData.setRanges(mapperRange.map(delinquencyBucket.getRanges()));
|
||||
return delinquencyBucketData;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -102,24 +91,12 @@ public class DelinquencyReadPlatformServiceImpl implements DelinquencyReadPlatfo
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class DelinquencyBucketMappingMapper implements RowMapper<DelinquencyRangeData> {
|
||||
|
||||
public String schema() {
|
||||
return " dran.id as id, dran.classification as classification, dran.min_age_days as minimumAgeDays, dran.max_age_days as maximumAgeDays from m_delinquency_bucket buc "
|
||||
+ " inner join m_delinquency_bucket_mappings dmap on dmap.delinquency_bucket_id = buc.id "
|
||||
+ " inner join m_delinquency_range dran on dran.id = dmap.delinquency_range_id ";
|
||||
}
|
||||
|
||||
@Override
|
||||
public DelinquencyRangeData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
|
||||
|
||||
final Long id = rs.getLong("id");
|
||||
final String classification = rs.getString("classification");
|
||||
final Integer minimumAgeDays = rs.getInt("minimumAgeDays");
|
||||
final Integer maximumAgeDays = rs.getInt("maximumAgeDays");
|
||||
|
||||
return new DelinquencyRangeData(id, classification, minimumAgeDays, maximumAgeDays);
|
||||
}
|
||||
@Override
|
||||
public Collection<LoanDelinquencyTagHistoryData> retrieveDelinquencyRangeHistory(Long loanId) {
|
||||
final Loan loan = this.loanAssembler.assembleFrom(loanId);
|
||||
final List<LoanDelinquencyTagHistory> loanDelinquencyTagData = this.repositoryLoanDelinquencyTagHistory
|
||||
.findByLoanOrderByAddedOnDateDesc(loan);
|
||||
return mapperLoanDelinquencyTagHistory.map(loanDelinquencyTagData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+4
@@ -37,4 +37,8 @@ public interface DelinquencyWritePlatformService {
|
||||
|
||||
CommandProcessingResult deleteDelinquencyBucket(Long delinquencyBucketId, JsonCommand command);
|
||||
|
||||
CommandProcessingResult applyDelinquencyTagToLoan(Long loanId, JsonCommand command);
|
||||
|
||||
void applyDelinquencyTagToLoan(Long loanId, Long ageDays);
|
||||
|
||||
}
|
||||
|
||||
+133
-38
@@ -27,6 +27,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
|
||||
import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
|
||||
@@ -48,12 +49,15 @@ import org.apache.fineract.portfolio.delinquency.exception.DelinquencyRangeInval
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
|
||||
import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlatformService {
|
||||
|
||||
private final DelinquencyBucketParseAndValidator dataValidatorBucket;
|
||||
@@ -65,6 +69,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
|
||||
private final LoanRepositoryWrapper loanRepository;
|
||||
private final LoanProductRepository loanProductRepository;
|
||||
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
|
||||
@@ -139,11 +144,40 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(delinquencyBucket.getId()).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandProcessingResult applyDelinquencyTagToLoan(Long loanId, JsonCommand command) {
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket();
|
||||
if (delinquencyBucket != null) {
|
||||
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
LoanRepaymentScheduleInstallment loanRepaymentSchedule = this.loanRepaymentScheduleInstallmentRepository
|
||||
.findFirstByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(loan, businessDate, null);
|
||||
|
||||
Long overdueDays = 0L;
|
||||
if (loanRepaymentSchedule != null) {
|
||||
overdueDays = DateUtils.getDifferenceInDays(loanRepaymentSchedule.getDueDate(), businessDate);
|
||||
}
|
||||
changes = lookUpDelinquencyRange(loan, delinquencyBucket, overdueDays);
|
||||
}
|
||||
return new CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(loan.getId()).with(changes).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyDelinquencyTagToLoan(Long loanId, Long ageDays) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
final DelinquencyBucket delinquencyBucket = loan.getLoanProduct().getDelinquencyBucket();
|
||||
if (delinquencyBucket != null) {
|
||||
lookUpDelinquencyRange(loan, delinquencyBucket, ageDays);
|
||||
}
|
||||
}
|
||||
|
||||
private DelinquencyRange createDelinquencyRange(DelinquencyRangeData data, Map<String, Object> changes) {
|
||||
Optional<DelinquencyRange> delinquencyRange = repositoryRange.findByClassification(data.getClassification());
|
||||
|
||||
if (delinquencyRange.isEmpty()) {
|
||||
if (data.getMinimumAgeDays() > data.getMaximumAgeDays()) {
|
||||
if (data.getMaximumAgeDays() != null && data.getMinimumAgeDays() > data.getMaximumAgeDays()) {
|
||||
final String errorMessage = "The age days values are invalid, the maximum age days can't be lower than minimum age days";
|
||||
throw new DelinquencyRangeInvalidAgesException(errorMessage, data.getMinimumAgeDays(), data.getMaximumAgeDays());
|
||||
}
|
||||
@@ -222,18 +256,12 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
|
||||
private void validateDelinquencyRanges(List<DelinquencyRange> ranges) {
|
||||
// Sort the ranges based on the minAgeDays
|
||||
final Comparator<DelinquencyRange> orderByMinAge = new Comparator<DelinquencyRange>() {
|
||||
ranges = sortDelinquencyRangesByMinAge(ranges);
|
||||
|
||||
@Override
|
||||
public int compare(DelinquencyRange o1, DelinquencyRange o2) {
|
||||
return o1.getMinimumAgeDays().compareTo(o2.getMinimumAgeDays());
|
||||
}
|
||||
};
|
||||
Collections.sort(ranges, orderByMinAge);
|
||||
DelinquencyRange prevDelinquencyRange = null;
|
||||
for (DelinquencyRange delinquencyRange : ranges) {
|
||||
if (prevDelinquencyRange != null) {
|
||||
if (isOverlaped(prevDelinquencyRange, delinquencyRange)) {
|
||||
if (isOverlapped(prevDelinquencyRange, delinquencyRange)) {
|
||||
final String errorMessage = "The delinquency ranges age days values are overlaped";
|
||||
throw new DelinquencyBucketAgesOverlapedException(errorMessage, prevDelinquencyRange, delinquencyRange);
|
||||
}
|
||||
@@ -242,37 +270,104 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOverlaped(DelinquencyRange o1, DelinquencyRange o2) {
|
||||
return Math.max(o1.getMinimumAgeDays(), o2.getMinimumAgeDays()) <= Math.min(o1.getMaximumAgeDays(), o2.getMaximumAgeDays());
|
||||
}
|
||||
|
||||
public void setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
|
||||
final LocalDate transactionDate = DateUtils.getLocalDateOfTenant();
|
||||
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = this.loanDelinquencyTagRepository
|
||||
.findByLoanOrderByAddedOnDateDesc(loan);
|
||||
// The delinquencyRangeId in null means just goes out from Delinquency
|
||||
if (delinquencyRangeId == null) {
|
||||
// The Loan will go out from Delinquency
|
||||
if (!loanDelinquencyTagHistory.isEmpty()) {
|
||||
LoanDelinquencyTagHistory loanDelinquencyTagPrev = loanDelinquencyTagHistory.get(0);
|
||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||
this.loanDelinquencyTagRepository.saveAndFlush(loanDelinquencyTagPrev);
|
||||
}
|
||||
private boolean isOverlapped(DelinquencyRange o1, DelinquencyRange o2) {
|
||||
if (o2.getMaximumAgeDays() != null) { // Max Age undefined - Last one
|
||||
return Math.max(o1.getMinimumAgeDays(), o2.getMinimumAgeDays()) <= Math.min(o1.getMaximumAgeDays(), o2.getMaximumAgeDays());
|
||||
} else {
|
||||
// The previous Loan Delinquency Tag will set as Lifted
|
||||
if (!loanDelinquencyTagHistory.isEmpty()) {
|
||||
LoanDelinquencyTagHistory loanDelinquencyTagPrev = loanDelinquencyTagHistory.get(0);
|
||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||
this.loanDelinquencyTagRepository.save(loanDelinquencyTagPrev);
|
||||
}
|
||||
|
||||
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
|
||||
LoanDelinquencyTagHistory loanDelinquencyTag = new LoanDelinquencyTagHistory(delinquencyRange, loan, transactionDate, null);
|
||||
loanDelinquencyTagHistory.add(loanDelinquencyTag);
|
||||
this.loanDelinquencyTagRepository.saveAndFlush(loanDelinquencyTag);
|
||||
return Math.max(o1.getMinimumAgeDays(), o2.getMinimumAgeDays()) <= Math.min(o1.getMaximumAgeDays(), o2.getMinimumAgeDays());
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> lookUpDelinquencyRange(final Loan loan, final DelinquencyBucket delinquencyBucket, long ageDays) {
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
|
||||
if (ageDays <= 0) { // No Delinquency
|
||||
log.debug("Loan {} without delinquency range with {} days", loan.getId(), ageDays);
|
||||
changes = setLoanDelinquencyTag(loan, null);
|
||||
|
||||
} else {
|
||||
// Sort the ranges based on the minAgeDays
|
||||
List<DelinquencyRange> ranges = sortDelinquencyRangesByMinAge(delinquencyBucket.getRanges());
|
||||
|
||||
for (DelinquencyRange delinquencyRange : ranges) {
|
||||
if (delinquencyRange.getMaximumAgeDays() == null) {
|
||||
if (delinquencyRange.getMinimumAgeDays() <= ageDays) {
|
||||
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
||||
ageDays);
|
||||
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (delinquencyRange.getMinimumAgeDays() <= ageDays && delinquencyRange.getMaximumAgeDays() >= ageDays) {
|
||||
log.debug("Loan {} with delinquency range {} with {} days", loan.getId(), delinquencyRange.getClassification(),
|
||||
ageDays);
|
||||
changes = setLoanDelinquencyTag(loan, delinquencyRange.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
changes.put("ageDays", ageDays);
|
||||
return changes;
|
||||
}
|
||||
|
||||
public Map<String, Object> setLoanDelinquencyTag(Long loanId, Long delinquencyRangeId) {
|
||||
final Loan loan = this.loanRepository.findOneWithNotFoundDetection(loanId);
|
||||
return setLoanDelinquencyTag(loan, delinquencyRangeId);
|
||||
}
|
||||
|
||||
public Map<String, Object> setLoanDelinquencyTag(Loan loan, Long delinquencyRangeId) {
|
||||
Map<String, Object> changes = new HashMap<>();
|
||||
List<LoanDelinquencyTagHistory> loanDelinquencyTagHistory = new ArrayList<>();
|
||||
final LocalDate transactionDate = DateUtils.getBusinessLocalDate();
|
||||
Optional<LoanDelinquencyTagHistory> optLoanDelinquencyTag = this.loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(loan, null);
|
||||
// The delinquencyRangeId in null means just goes out from Delinquency
|
||||
LoanDelinquencyTagHistory loanDelinquencyTagPrev = null;
|
||||
if (delinquencyRangeId == null) {
|
||||
// The Loan will go out from Delinquency
|
||||
if (optLoanDelinquencyTag.isPresent()) {
|
||||
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
||||
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||
}
|
||||
} else {
|
||||
if (optLoanDelinquencyTag.isPresent()) {
|
||||
loanDelinquencyTagPrev = optLoanDelinquencyTag.get();
|
||||
}
|
||||
// If the Delinquency Tag has not changed
|
||||
if (loanDelinquencyTagPrev != null && loanDelinquencyTagPrev.getDelinquencyRange().getId().equals(delinquencyRangeId)) {
|
||||
changes.put("current", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||
} else {
|
||||
// The previous Loan Delinquency Tag will set as Lifted
|
||||
if (loanDelinquencyTagPrev != null) {
|
||||
loanDelinquencyTagPrev.setLiftedOnDate(transactionDate);
|
||||
loanDelinquencyTagHistory.add(loanDelinquencyTagPrev);
|
||||
changes.put("previous", loanDelinquencyTagPrev.getDelinquencyRange());
|
||||
}
|
||||
|
||||
final DelinquencyRange delinquencyRange = repositoryRange.getReferenceById(delinquencyRangeId);
|
||||
LoanDelinquencyTagHistory loanDelinquencyTag = new LoanDelinquencyTagHistory(delinquencyRange, loan, transactionDate, null);
|
||||
loanDelinquencyTagHistory.add(loanDelinquencyTag);
|
||||
changes.put("current", loanDelinquencyTag.getDelinquencyRange());
|
||||
}
|
||||
}
|
||||
if (loanDelinquencyTagHistory.size() > 0) {
|
||||
this.loanDelinquencyTagRepository.saveAllAndFlush(loanDelinquencyTagHistory);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
private List<DelinquencyRange> sortDelinquencyRangesByMinAge(List<DelinquencyRange> ranges) {
|
||||
final Comparator<DelinquencyRange> orderByMinAge = new Comparator<DelinquencyRange>() {
|
||||
|
||||
@Override
|
||||
public int compare(DelinquencyRange o1, DelinquencyRange o2) {
|
||||
return o1.getMinimumAgeDays().compareTo(o2.getMinimumAgeDays());
|
||||
}
|
||||
};
|
||||
Collections.sort(ranges, orderByMinAge);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-104
@@ -54,6 +54,7 @@ import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.fineract.commands.domain.CommandWrapper;
|
||||
import org.apache.fineract.commands.service.CommandWrapperBuilder;
|
||||
@@ -100,6 +101,9 @@ import org.apache.fineract.portfolio.charge.service.ChargeReadPlatformService;
|
||||
import org.apache.fineract.portfolio.client.data.ClientData;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.data.LoanCollateralResponseData;
|
||||
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralManagementReadPlatformService;
|
||||
import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiResourceSwagger;
|
||||
import org.apache.fineract.portfolio.delinquency.data.LoanDelinquencyTagHistoryData;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatformService;
|
||||
import org.apache.fineract.portfolio.floatingrates.data.InterestRatePeriodData;
|
||||
import org.apache.fineract.portfolio.fund.data.FundData;
|
||||
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
|
||||
@@ -201,6 +205,7 @@ import org.springframework.util.CollectionUtils;
|
||||
+ "Specifies rest frequency start date for interest recalculation. This date must be before or equal to disbursement date\n"
|
||||
+ "recalculationCompoundingFrequencyDate\n"
|
||||
+ "Specifies compounding frequency start date for interest recalculation. This date must be equal to disbursement date")
|
||||
@RequiredArgsConstructor
|
||||
public class LoansApiResource {
|
||||
|
||||
private final Set<String> loanDataParameters = new HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId", "clientId",
|
||||
@@ -259,63 +264,8 @@ public class LoansApiResource {
|
||||
private final DefaultToApiJsonSerializer<GlimRepaymentTemplate> glimTemplateToApiJsonSerializer;
|
||||
private final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService;
|
||||
private final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService;
|
||||
|
||||
public LoansApiResource(final PlatformSecurityContext context, final LoanReadPlatformService loanReadPlatformService,
|
||||
final LoanProductReadPlatformService loanProductReadPlatformService,
|
||||
final LoanDropdownReadPlatformService dropdownReadPlatformService, final FundReadPlatformService fundReadPlatformService,
|
||||
final ChargeReadPlatformService chargeReadPlatformService, final LoanChargeReadPlatformService loanChargeReadPlatformService,
|
||||
final LoanScheduleCalculationPlatformService calculationPlatformService,
|
||||
final GuarantorReadPlatformService guarantorReadPlatformService,
|
||||
final CodeValueReadPlatformService codeValueReadPlatformService, final GroupReadPlatformService groupReadPlatformService,
|
||||
final DefaultToApiJsonSerializer<LoanAccountData> toApiJsonSerializer,
|
||||
final DefaultToApiJsonSerializer<LoanApprovalData> loanApprovalDataToApiJsonSerializer,
|
||||
final DefaultToApiJsonSerializer<LoanScheduleData> loanScheduleToApiJsonSerializer,
|
||||
final ApiRequestParameterHelper apiRequestParameterHelper, final FromJsonHelper fromJsonHelper,
|
||||
final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService,
|
||||
final CalendarReadPlatformService calendarReadPlatformService, final NoteReadPlatformService noteReadPlatformService,
|
||||
final PortfolioAccountReadPlatformService portfolioAccountReadPlatformServiceImpl,
|
||||
final AccountAssociationsReadPlatformService accountAssociationsReadPlatformService,
|
||||
final LoanScheduleHistoryReadPlatformService loanScheduleHistoryReadPlatformService,
|
||||
final AccountDetailsReadPlatformService accountDetailsReadPlatformService,
|
||||
final EntityDatatableChecksReadService entityDatatableChecksReadService,
|
||||
final BulkImportWorkbookService bulkImportWorkbookService,
|
||||
final BulkImportWorkbookPopulatorService bulkImportWorkbookPopulatorService, final RateReadService rateReadService,
|
||||
final ConfigurationDomainService configurationDomainService,
|
||||
final DefaultToApiJsonSerializer<GlimRepaymentTemplate> glimTemplateToApiJsonSerializer,
|
||||
final GLIMAccountInfoReadPlatformService glimAccountInfoReadPlatformService,
|
||||
final LoanCollateralManagementReadPlatformService loanCollateralManagementReadPlatformService) {
|
||||
this.context = context;
|
||||
this.loanReadPlatformService = loanReadPlatformService;
|
||||
this.loanProductReadPlatformService = loanProductReadPlatformService;
|
||||
this.dropdownReadPlatformService = dropdownReadPlatformService;
|
||||
this.fundReadPlatformService = fundReadPlatformService;
|
||||
this.chargeReadPlatformService = chargeReadPlatformService;
|
||||
this.loanChargeReadPlatformService = loanChargeReadPlatformService;
|
||||
this.calculationPlatformService = calculationPlatformService;
|
||||
this.guarantorReadPlatformService = guarantorReadPlatformService;
|
||||
this.codeValueReadPlatformService = codeValueReadPlatformService;
|
||||
this.groupReadPlatformService = groupReadPlatformService;
|
||||
this.toApiJsonSerializer = toApiJsonSerializer;
|
||||
this.loanApprovalDataToApiJsonSerializer = loanApprovalDataToApiJsonSerializer;
|
||||
this.loanScheduleToApiJsonSerializer = loanScheduleToApiJsonSerializer;
|
||||
this.apiRequestParameterHelper = apiRequestParameterHelper;
|
||||
this.fromJsonHelper = fromJsonHelper;
|
||||
this.commandsSourceWritePlatformService = commandsSourceWritePlatformService;
|
||||
this.calendarReadPlatformService = calendarReadPlatformService;
|
||||
this.noteReadPlatformService = noteReadPlatformService;
|
||||
this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformServiceImpl;
|
||||
this.accountAssociationsReadPlatformService = accountAssociationsReadPlatformService;
|
||||
this.loanScheduleHistoryReadPlatformService = loanScheduleHistoryReadPlatformService;
|
||||
this.accountDetailsReadPlatformService = accountDetailsReadPlatformService;
|
||||
this.entityDatatableChecksReadService = entityDatatableChecksReadService;
|
||||
this.rateReadService = rateReadService;
|
||||
this.bulkImportWorkbookService = bulkImportWorkbookService;
|
||||
this.bulkImportWorkbookPopulatorService = bulkImportWorkbookPopulatorService;
|
||||
this.configurationDomainService = configurationDomainService;
|
||||
this.glimTemplateToApiJsonSerializer = glimTemplateToApiJsonSerializer;
|
||||
this.glimAccountInfoReadPlatformService = glimAccountInfoReadPlatformService;
|
||||
this.loanCollateralManagementReadPlatformService = loanCollateralManagementReadPlatformService;
|
||||
}
|
||||
private final DefaultToApiJsonSerializer<LoanDelinquencyTagHistoryData> jsonSerializerTagHistory;
|
||||
private final DelinquencyReadPlatformService delinquencyReadPlatformService;
|
||||
|
||||
/*
|
||||
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
|
||||
@@ -886,51 +836,37 @@ public class LoansApiResource {
|
||||
@Parameter(hidden = true) final String apiRequestBodyAsJson) {
|
||||
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||
|
||||
CommandProcessingResult result = null;
|
||||
|
||||
CommandWrapper commandRequest = null;
|
||||
if (is(commandParam, "reject")) {
|
||||
final CommandWrapper commandRequest = builder.rejectLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.rejectLoanApplication(loanId).build();
|
||||
} else if (is(commandParam, "withdrawnByApplicant")) {
|
||||
final CommandWrapper commandRequest = builder.withdrawLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.withdrawLoanApplication(loanId).build();
|
||||
} else if (is(commandParam, "approve")) {
|
||||
final CommandWrapper commandRequest = builder.approveLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.approveLoanApplication(loanId).build();
|
||||
} else if (is(commandParam, "disburse")) {
|
||||
final CommandWrapper commandRequest = builder.disburseLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.disburseLoanApplication(loanId).build();
|
||||
} else if (is(commandParam, "disburseToSavings")) {
|
||||
final CommandWrapper commandRequest = builder.disburseLoanToSavingsApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
if (is(commandParam, "undoapproval")) {
|
||||
final CommandWrapper commandRequest = builder.undoLoanApplicationApproval(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.disburseLoanToSavingsApplication(loanId).build();
|
||||
} else if (is(commandParam, "undoapproval")) {
|
||||
commandRequest = builder.undoLoanApplicationApproval(loanId).build();
|
||||
} else if (is(commandParam, "undodisbursal")) {
|
||||
final CommandWrapper commandRequest = builder.undoLoanApplicationDisbursal(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.undoLoanApplicationDisbursal(loanId).build();
|
||||
} else if (is(commandParam, "undolastdisbursal")) {
|
||||
final CommandWrapper commandRequest = builder.undoLastDisbursalLoanApplication(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
}
|
||||
|
||||
if (is(commandParam, "assignloanofficer")) {
|
||||
final CommandWrapper commandRequest = builder.assignLoanOfficer(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.undoLastDisbursalLoanApplication(loanId).build();
|
||||
} else if (is(commandParam, "assignloanofficer")) {
|
||||
commandRequest = builder.assignLoanOfficer(loanId).build();
|
||||
} else if (is(commandParam, "unassignloanofficer")) {
|
||||
final CommandWrapper commandRequest = builder.unassignLoanOfficer(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.unassignLoanOfficer(loanId).build();
|
||||
} else if (is(commandParam, "recoverGuarantees")) {
|
||||
final CommandWrapper commandRequest = new CommandWrapperBuilder().recoverFromGuarantor(loanId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = new CommandWrapperBuilder().recoverFromGuarantor(loanId).build();
|
||||
} else if (is(commandParam, "assigndelinquency")) {
|
||||
commandRequest = builder.assignDelinquency(loanId).build();
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
if (commandRequest == null) {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
@@ -978,31 +914,25 @@ public class LoansApiResource {
|
||||
|
||||
final CommandWrapperBuilder builder = new CommandWrapperBuilder().withJson(apiRequestBodyAsJson);
|
||||
|
||||
CommandProcessingResult result = null;
|
||||
|
||||
CommandWrapper commandRequest = null;
|
||||
if (is(commandParam, "reject")) {
|
||||
final CommandWrapper commandRequest = builder.rejectGLIMApplication(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.rejectGLIMApplication(glimId).build();
|
||||
} else if (is(commandParam, "approve")) {
|
||||
final CommandWrapper commandRequest = builder.approveGLIMLoanApplication(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.approveGLIMLoanApplication(glimId).build();
|
||||
} else if (is(commandParam, "disburse")) {
|
||||
final CommandWrapper commandRequest = builder.disburseGlimLoanApplication(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.disburseGlimLoanApplication(glimId).build();
|
||||
} else if (is(commandParam, "glimrepayment")) {
|
||||
final CommandWrapper commandRequest = builder.repaymentGlimLoanApplication(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.repaymentGlimLoanApplication(glimId).build();
|
||||
} else if (is(commandParam, "undodisbursal")) {
|
||||
final CommandWrapper commandRequest = builder.undoGLIMLoanDisbursal(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.undoGLIMLoanDisbursal(glimId).build();
|
||||
} else if (is(commandParam, "undoapproval")) {
|
||||
final CommandWrapper commandRequest = builder.undoGLIMLoanApproval(glimId).build();
|
||||
result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
commandRequest = builder.undoGLIMLoanApproval(glimId).build();
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
if (commandRequest == null) {
|
||||
throw new UnrecognizedQueryParamException("command", commandParam);
|
||||
}
|
||||
CommandProcessingResult result = this.commandsSourceWritePlatformService.logCommandSource(commandRequest);
|
||||
|
||||
return this.toApiJsonSerializer.serialize(result);
|
||||
}
|
||||
@@ -1040,4 +970,21 @@ public class LoansApiResource {
|
||||
uploadedInputStream, fileDetail, locale, dateFormat);
|
||||
return this.toApiJsonSerializer.serialize(importDocumentId);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{loanId}/delinquencytags")
|
||||
@Consumes({ MediaType.TEXT_HTML, MediaType.APPLICATION_JSON })
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Operation(summary = "Retrieve the Loan Delinquency Tag history using the Loan Id", description = "")
|
||||
@ApiResponses({
|
||||
@ApiResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = DelinquencyApiResourceSwagger.GetDelinquencyTagHistoryResponse.class))) })
|
||||
public String getDelinquencyTagHistory(@PathParam("loanId") @Parameter(description = "loanId") final Long loanId,
|
||||
@Context final UriInfo uriInfo) {
|
||||
context.authenticatedUser().validateHasReadPermission("DELINQUENCY_TAGS");
|
||||
final Collection<LoanDelinquencyTagHistoryData> loanDelinquencyTagHistoryData = this.delinquencyReadPlatformService
|
||||
.retrieveDelinquencyRangeHistory(loanId);
|
||||
ApiRequestJsonSerializationSettings settings = apiRequestParameterHelper.process(uriInfo.getQueryParameters());
|
||||
return this.jsonSerializerTagHistory.serialize(settings, loanDelinquencyTagHistoryData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* 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.loanaccount.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDate;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
public class LoanScheduleDelinquencyData implements Serializable {
|
||||
|
||||
private final Long loanId;
|
||||
private final Long productId;
|
||||
private final LocalDate dueDate;
|
||||
private final LocalDate fromDate;
|
||||
private final Long ageDays;
|
||||
|
||||
}
|
||||
+7
@@ -18,10 +18,17 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
public interface LoanRepaymentScheduleInstallmentRepository
|
||||
extends JpaRepository<LoanRepaymentScheduleInstallment, Long>, JpaSpecificationExecutor<LoanRepaymentScheduleInstallment> {
|
||||
|
||||
LoanRepaymentScheduleInstallment findFirstByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(Loan loan,
|
||||
LocalDate businnessDate, LocalDate obligationsMetOnDate);
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> findByLoanAndDueDateLessThanEqualAndObligationsMetOnDateOrderByDueDate(Loan loan,
|
||||
LocalDate businnessDate, LocalDate obligationsMetOnDate);
|
||||
}
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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.loanaccount.jobs.setloandelinquencytags;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.jobs.service.JobName;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.Step;
|
||||
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
|
||||
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
|
||||
import org.springframework.batch.core.launch.support.RunIdIncrementer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
@Configuration
|
||||
@AllArgsConstructor
|
||||
public class SetLoanDelinquencyTagsConfig {
|
||||
|
||||
private JobBuilderFactory jobs;
|
||||
private StepBuilderFactory steps;
|
||||
|
||||
private LoanProductRepository loanProductRepository;
|
||||
private DelinquencyWritePlatformService delinquencyWritePlatformService;
|
||||
private LoanReadPlatformService loanReadPlatformService;
|
||||
|
||||
@Bean
|
||||
public Step setLoanDelinquencyTagsStep() {
|
||||
return steps.get(JobName.LOAN_DELINQUENCY_CLASSIFICATION.name()).tasklet(setLoanDelinquencyTagsTasklet()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Job setLoanDelinquencyTagsJob() {
|
||||
return jobs.get(JobName.LOAN_DELINQUENCY_CLASSIFICATION.name()).start(setLoanDelinquencyTagsStep())
|
||||
.incrementer(new RunIdIncrementer()).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SetLoanDelinquencyTagsTasklet setLoanDelinquencyTagsTasklet() {
|
||||
return new SetLoanDelinquencyTagsTasklet(loanProductRepository, delinquencyWritePlatformService, loanReadPlatformService);
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.loanaccount.jobs.setloandelinquencytags;
|
||||
|
||||
import java.util.Collection;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
|
||||
import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformService;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
|
||||
import org.springframework.batch.core.StepContribution;
|
||||
import org.springframework.batch.core.scope.context.ChunkContext;
|
||||
import org.springframework.batch.core.step.tasklet.Tasklet;
|
||||
import org.springframework.batch.repeat.RepeatStatus;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class SetLoanDelinquencyTagsTasklet implements Tasklet {
|
||||
|
||||
private final LoanProductRepository loanProductRepository;
|
||||
private final DelinquencyWritePlatformService delinquencyWritePlatformService;
|
||||
private final LoanReadPlatformService loanReadPlatformService;
|
||||
|
||||
@Override
|
||||
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
|
||||
Collection<LoanScheduleDelinquencyData> loanScheduleDelinquencyData = this.loanReadPlatformService
|
||||
.retrieveScheduleDelinquencyData(DateUtils.getBusinessLocalDate());
|
||||
log.debug("Were found {} items", loanScheduleDelinquencyData.size());
|
||||
for (LoanScheduleDelinquencyData loanDelinquencyData : loanScheduleDelinquencyData) {
|
||||
this.delinquencyWritePlatformService.applyDelinquencyTagToLoan(loanDelinquencyData.getLoanId(),
|
||||
loanDelinquencyData.getAgeDays());
|
||||
}
|
||||
|
||||
log.debug("{}: Records affected by setLoanDelinquencyTags: {}", ThreadLocalContextUtil.getTenant().getName(),
|
||||
loanScheduleDelinquencyData.size());
|
||||
return RepeatStatus.FINISHED;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-1
@@ -40,7 +40,8 @@ public class LoanAccrualPlatformServiceImpl implements LoanAccrualPlatformServic
|
||||
|
||||
@Override
|
||||
public void addPeriodicAccruals(final LocalDate tillDate) throws JobExecutionException {
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = this.loanReadPlatformService.retrivePeriodicAccrualData(tillDate);
|
||||
Collection<LoanScheduleAccrualData> loanScheduleAccrualDataList = this.loanReadPlatformService
|
||||
.retrievePeriodicAccrualData(tillDate);
|
||||
addPeriodicAccruals(tillDate, loanScheduleAccrualDataList);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -33,6 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
|
||||
@@ -104,11 +105,13 @@ public interface LoanReadPlatformService {
|
||||
|
||||
Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
|
||||
|
||||
Collection<LoanScheduleDelinquencyData> retrieveScheduleDelinquencyData(LocalDate businessLocalDate);
|
||||
|
||||
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
|
||||
|
||||
LoanTransactionData retrieveLoanWriteoffTemplate(Long loanId);
|
||||
|
||||
Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(LocalDate tillDate);
|
||||
Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(LocalDate tillDate);
|
||||
|
||||
Collection<Long> fetchLoansForInterestRecalculation();
|
||||
|
||||
|
||||
+46
-1
@@ -88,6 +88,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanApprovalData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInterestRecalculationData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInstallmentData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
@@ -1647,6 +1648,22 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoanScheduleDelinquencyData> retrieveScheduleDelinquencyData(LocalDate businessLocalDate) {
|
||||
LoanScheduleDelinquencyMapper mapper = new LoanScheduleDelinquencyMapper(DateUtils.getBusinessLocalDate());
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append("select ").append(mapper.schema())
|
||||
.append(" where loan.loan_status_id=:active and ls.duedate <= :businessLocalDate and ")
|
||||
.append(" mpl.delinquency_bucket_id is not null "); // Jusr the Loan Product linked to delinquency
|
||||
// bucket
|
||||
sqlBuilder.append(" order by loan.id, ls.duedate ");
|
||||
Map<String, Object> paramMap = new HashMap<>(3);
|
||||
paramMap.put("active", LoanStatus.ACTIVE.getValue());
|
||||
paramMap.put("businessLocalDate", businessLocalDate);
|
||||
|
||||
return this.namedParameterJdbcTemplate.query(sqlBuilder.toString(), paramMap, mapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
|
||||
|
||||
@@ -1673,7 +1690,7 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<LoanScheduleAccrualData> retrivePeriodicAccrualData(final LocalDate tillDate) {
|
||||
public Collection<LoanScheduleAccrualData> retrievePeriodicAccrualData(final LocalDate tillDate) {
|
||||
|
||||
LoanSchedulePeriodicAccrualMapper mapper = new LoanSchedulePeriodicAccrualMapper();
|
||||
LocalDate organisationStartDate = this.configurationDomainService.retrieveOrganisationStartDate();
|
||||
@@ -1816,6 +1833,34 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService {
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LoanScheduleDelinquencyMapper implements RowMapper<LoanScheduleDelinquencyData> {
|
||||
|
||||
private LocalDate businessDate;
|
||||
|
||||
LoanScheduleDelinquencyMapper(LocalDate businessDate) {
|
||||
this.businessDate = businessDate;
|
||||
}
|
||||
|
||||
public String schema() {
|
||||
final StringBuilder sqlBuilder = new StringBuilder(400);
|
||||
sqlBuilder.append(" loan.id as loanId, ls.duedate as duedate, ls.fromdate as fromdate, loan.product_id as productId ")
|
||||
.append(" from m_loan_repayment_schedule ls left join m_loan loan on loan.id=ls.loan_id ")
|
||||
.append(" left join m_product_loan mpl on mpl.id = loan.product_id ");
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanScheduleDelinquencyData mapRow(ResultSet rs, @SuppressWarnings("unused") int rowNum) throws SQLException {
|
||||
|
||||
final Long loanId = rs.getLong("loanId");
|
||||
final Long productId = rs.getLong("productId");
|
||||
final LocalDate dueDate = JdbcSupport.getLocalDate(rs, "duedate");
|
||||
final LocalDate fromDate = JdbcSupport.getLocalDate(rs, "fromdate");
|
||||
final long ageDays = DateUtils.getDifferenceInDays(dueDate, businessDate);
|
||||
return new LoanScheduleDelinquencyData(loanId, productId, dueDate, fromDate, ageDays);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId) {
|
||||
final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
|
||||
|
||||
+2
@@ -31,4 +31,6 @@ public interface LoanProductRepository extends JpaRepository<LoanProduct, Long>,
|
||||
List<LoanProduct> retrieveLoanProductsByChargeId(@Param("chargeId") Long chargeId);
|
||||
|
||||
Long countByDelinquencyBucket(DelinquencyBucket delinquencyBucket);
|
||||
|
||||
List<LoanProduct> findByDelinquencyBucketNotNull();
|
||||
}
|
||||
|
||||
+6
@@ -143,6 +143,12 @@ public class LoanProductWritePlatformServiceJpaRepositoryImpl implements LoanPro
|
||||
this.aprCalculator, floatingRate, rates);
|
||||
loanProduct.updateLoanProductInRelatedClasses();
|
||||
|
||||
if (command.parameterExists("delinquencyBucketId")) {
|
||||
DelinquencyBucket delinquencyBucket = this.delinquencyBucketRepository
|
||||
.getReferenceById(command.longValueOfParameterNamed("delinquencyBucketId"));
|
||||
loanProduct.setDelinquencyBucket(delinquencyBucket);
|
||||
}
|
||||
|
||||
this.loanProductRepository.saveAndFlush(loanProduct);
|
||||
|
||||
// save accounting mappings
|
||||
|
||||
@@ -59,4 +59,5 @@
|
||||
<include file="parts/0037_add_loan_cob_job_data.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0038_add_reversal_data_to_loan_transaction.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0039_add_loan_account_locks.xml" relativeToChangelogFile="true"/>
|
||||
<include file="parts/0040_add_delinquency_tags_job.xml" relativeToChangelogFile="true"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
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.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd">
|
||||
<changeSet author="fineract" id="delinquency_tags_job-1">
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="READ_DELINQUENCY_TAGS"/>
|
||||
<column name="entity_name" value="DELINQUENCY_TAGS"/>
|
||||
<column name="action_name" value="READ"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="UPDATE_DELINQUENCY_TAGS"/>
|
||||
<column name="entity_name" value="DELINQUENCY_TAGS"/>
|
||||
<column name="action_name" value="UPDATE"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
<insert tableName="m_permission">
|
||||
<column name="grouping" value="organisation"/>
|
||||
<column name="code" value="UPDATEDELINQUENCY_LOAN"/>
|
||||
<column name="entity_name" value="LOAN"/>
|
||||
<column name="action_name" value="UPDATEDELINQUENCY"/>
|
||||
<column name="can_maker_checker" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="delinquency_tags_job-2">
|
||||
<insert tableName="job">
|
||||
<column name="name" value="Loan Delinquency Classification"/>
|
||||
<column name="display_name" value="Loan Delinquency Classification"/>
|
||||
<column name="cron_expression" value="0 0 22 1/1 * ? *"/>
|
||||
<column name="create_time" valueDate="${current_datetime}"/>
|
||||
<column name="task_priority" valueNumeric="5"/>
|
||||
<column name="group_name"/>
|
||||
<column name="previous_run_start_time"/>
|
||||
<column name="job_key" value="Loan Delinquency ClassificationDetail1 _ DEFAULT"/>
|
||||
<column name="initializing_errorlog"/>
|
||||
<column name="is_active" valueBoolean="true"/>
|
||||
<column name="currently_running" valueBoolean="false"/>
|
||||
<column name="updates_allowed" valueBoolean="true"/>
|
||||
<column name="scheduler_group" valueNumeric="0"/>
|
||||
<column name="is_misfired" valueBoolean="false"/>
|
||||
<column name="node_id" valueNumeric="1"/>
|
||||
<column name="is_mismatched_job" valueBoolean="true"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
+78
@@ -27,21 +27,32 @@ import io.restassured.builder.ResponseSpecBuilder;
|
||||
import io.restassured.http.ContentType;
|
||||
import io.restassured.specification.RequestSpecification;
|
||||
import io.restassured.specification.ResponseSpecification;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.DeleteDelinquencyBucketResponse;
|
||||
import org.apache.fineract.client.models.DeleteDelinquencyRangeResponse;
|
||||
import org.apache.fineract.client.models.GetDelinquencyBucketsResponse;
|
||||
import org.apache.fineract.client.models.GetDelinquencyRangesResponse;
|
||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostDelinquencyBucketResponse;
|
||||
import org.apache.fineract.client.models.PostDelinquencyRangeResponse;
|
||||
import org.apache.fineract.client.models.PutDelinquencyBucketResponse;
|
||||
import org.apache.fineract.client.models.PutDelinquencyRangeResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.SchedulerJobHelper;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
|
||||
import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper;
|
||||
import org.apache.fineract.integrationtests.common.products.DelinquencyRangesHelper;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@Slf4j
|
||||
public class DelinquencyBucketsIntegrationTest {
|
||||
|
||||
private ResponseSpecification responseSpec;
|
||||
@@ -208,4 +219,71 @@ public class DelinquencyBucketsIntegrationTest {
|
||||
DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, response403Spec, jsonBucket);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanClassificationJob() {
|
||||
// Given
|
||||
final LoanTransactionHelper loanTransactionHelper = new LoanTransactionHelper(this.requestSpec, this.responseSpec);
|
||||
final SchedulerJobHelper schedulerJobHelper = new SchedulerJobHelper(requestSpec);
|
||||
|
||||
ArrayList<Integer> rangeIds = new ArrayList<>();
|
||||
String jsonRange = DelinquencyRangesHelper.getAsJSON(1, 3);
|
||||
PostDelinquencyRangeResponse delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec,
|
||||
jsonRange);
|
||||
rangeIds.add(delinquencyRangeResponse.getResourceId());
|
||||
jsonRange = DelinquencyRangesHelper.getAsJSON(4, 60);
|
||||
// Create
|
||||
delinquencyRangeResponse = DelinquencyRangesHelper.createDelinquencyRange(requestSpec, responseSpec, jsonRange);
|
||||
rangeIds.add(delinquencyRangeResponse.getResourceId());
|
||||
|
||||
final GetDelinquencyRangesResponse range = DelinquencyRangesHelper.getDelinquencyRange(requestSpec, responseSpec,
|
||||
delinquencyRangeResponse.getResourceId());
|
||||
final String classificationExpected = range.getClassification();
|
||||
log.info("Expected Delinquency Range classification {}", classificationExpected);
|
||||
|
||||
String jsonBucket = DelinquencyBucketsHelper.getAsJSON(rangeIds);
|
||||
PostDelinquencyBucketResponse delinquencyBucketResponse = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec,
|
||||
responseSpec, jsonBucket);
|
||||
final GetDelinquencyBucketsResponse delinquencyBucket = DelinquencyBucketsHelper.getDelinquencyBucket(requestSpec, responseSpec,
|
||||
delinquencyBucketResponse.getResourceId());
|
||||
|
||||
final Integer clientId = ClientHelper.createClient(this.requestSpec, this.responseSpec, "01 January 2012");
|
||||
final HashMap<String, Object> loanProductMap = new LoanProductTestBuilder().build(null, delinquencyBucket.getId());
|
||||
final Integer loanProductId = loanTransactionHelper.getLoanProductId(Utils.convertToJson(loanProductMap));
|
||||
|
||||
final GetLoanProductsProductIdResponse getLoanProductsProductResponse = loanTransactionHelper.getLoanProduct(loanProductId);
|
||||
log.info("Loan Product Bucket Name: {}", getLoanProductsProductResponse.getDelinquencyBucket().getName());
|
||||
|
||||
LocalDate todaysDate = Utils.getLocalDateOfTenant();
|
||||
todaysDate = todaysDate.minusDays(40);
|
||||
final String operationDate = Utils.dateFormatter.format(todaysDate);
|
||||
final String principalAmount = "10000";
|
||||
|
||||
final String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(principalAmount).withLoanTermFrequency("12")
|
||||
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("12").withRepaymentEveryAfter("1")
|
||||
.withRepaymentFrequencyTypeAsMonths() //
|
||||
.withInterestRatePerPeriod("2") //
|
||||
.withExpectedDisbursementDate(operationDate) //
|
||||
.withInterestTypeAsDecliningBalance() //
|
||||
.withSubmittedOnDate(operationDate) //
|
||||
.build(clientId.toString(), loanProductId.toString(), null);
|
||||
final Integer loanId = loanTransactionHelper.getLoanId(loanApplicationJSON);
|
||||
loanTransactionHelper.approveLoan(operationDate, principalAmount, loanId, null);
|
||||
loanTransactionHelper.disburseLoanWithNetDisbursalAmount(operationDate, loanId, principalAmount);
|
||||
|
||||
// When
|
||||
final String jobName = "Loan Delinquency Classification";
|
||||
schedulerJobHelper.executeAndAwaitJob(jobName);
|
||||
|
||||
final GetLoansLoanIdResponse getLoansLoanIdResponse = loanTransactionHelper.getLoan(requestSpec, responseSpec, loanId);
|
||||
log.info("Loan Delinquency Range {}", getLoansLoanIdResponse.getDelinquencyRange().getClassification());
|
||||
|
||||
// Then
|
||||
assertNotNull(delinquencyBucketResponse);
|
||||
assertNotNull(getLoanProductsProductResponse);
|
||||
assertEquals(getLoanProductsProductResponse.getDelinquencyBucket().getName(), delinquencyBucket.getName());
|
||||
assertNotNull(getLoansLoanIdResponse);
|
||||
assertEquals(getLoansLoanIdResponse.getDelinquencyRange().getClassification(), classificationExpected);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ import java.time.temporal.ChronoField;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
@@ -421,4 +422,9 @@ public final class Utils {
|
||||
}
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
public static String convertToJson(HashMap<String, Object> map) {
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+12
-1
@@ -132,6 +132,11 @@ public class LoanProductTestBuilder {
|
||||
private String installmentAmountInMultiplesOf;
|
||||
|
||||
public String build(final String chargeId) {
|
||||
final HashMap<String, Object> map = build(chargeId, null);
|
||||
return new Gson().toJson(map);
|
||||
}
|
||||
|
||||
public HashMap<String, Object> build(final String chargeId, final Integer delinquencyBucketId) {
|
||||
final HashMap<String, Object> map = new HashMap<>();
|
||||
|
||||
if (chargeId != null) {
|
||||
@@ -234,7 +239,13 @@ public class LoanProductTestBuilder {
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
map.put("installmentAmountInMultiplesOf", this.installmentAmountInMultiplesOf);
|
||||
}
|
||||
return new Gson().toJson(map);
|
||||
|
||||
// Delinquency Bucket
|
||||
if (delinquencyBucketId != null) {
|
||||
map.put("delinquencyBucketId", delinquencyBucketId);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public LoanProductTestBuilder withInstallmentAmountInMultiplesOf(String installmentAmountInMultiplesOf) {
|
||||
|
||||
+19
@@ -38,6 +38,9 @@ import java.util.Map;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.util.JSON;
|
||||
import org.apache.fineract.integrationtests.common.CommonConstants;
|
||||
import org.apache.fineract.integrationtests.common.Utils;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
@@ -73,11 +76,20 @@ public class LoanTransactionHelper {
|
||||
|
||||
public static final String DATE_TIME_FORMAT = "dd MMMM yyyy HH:mm";
|
||||
|
||||
private static final Gson GSON = new JSON().getGson();
|
||||
|
||||
public LoanTransactionHelper(final RequestSpecification requestSpec, final ResponseSpecification responseSpec) {
|
||||
this.requestSpec = requestSpec;
|
||||
this.responseSpec = responseSpec;
|
||||
}
|
||||
|
||||
public GetLoanProductsProductIdResponse getLoanProduct(final Integer loanProductId) {
|
||||
final String GET_LOANPRODUCT_URL = "/fineract-provider/api/v1/loanproducts/" + loanProductId + "?" + Utils.TENANT_IDENTIFIER;
|
||||
final String response = Utils.performServerGet(this.requestSpec, this.responseSpec, GET_LOANPRODUCT_URL);
|
||||
log.info("Response {}", response);
|
||||
return GSON.fromJson(response, GetLoanProductsProductIdResponse.class);
|
||||
}
|
||||
|
||||
public Integer getLoanProductId(final String loanProductJSON) {
|
||||
return Utils.performServerPost(this.requestSpec, this.responseSpec, CREATE_LOAN_PRODUCT_URL, loanProductJSON, "resourceId");
|
||||
}
|
||||
@@ -157,6 +169,13 @@ public class LoanTransactionHelper {
|
||||
return Utils.performServerGet(requestSpec, responseSpec, URL, param);
|
||||
}
|
||||
|
||||
public GetLoansLoanIdResponse getLoan(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer loanId) {
|
||||
final String URL = "/fineract-provider/api/v1/loans/" + loanId + "?associations=all&" + Utils.TENANT_IDENTIFIER;
|
||||
final String response = Utils.performServerGet(requestSpec, responseSpec, URL);
|
||||
return GSON.fromJson(response, GetLoansLoanIdResponse.class);
|
||||
}
|
||||
|
||||
public Object getLoanDetailExcludeFutureSchedule(final RequestSpecification requestSpec, final ResponseSpecification responseSpec,
|
||||
final Integer loanID, final String param) {
|
||||
final String URL = "/fineract-provider/api/v1/loans/" + loanID + "?associations=all&exclude=guarantors,futureSchedule&"
|
||||
|
||||
Reference in New Issue
Block a user