FINERACT-2081: Add all loan term validations to loan details

This commit is contained in:
Oleksii Novikov
2024-10-12 08:05:30 +02:00
committed by Arnold Gálovics
parent fe16b61c72
commit 25866edfc9
18 changed files with 383 additions and 161 deletions
@@ -20,7 +20,6 @@ package org.apache.fineract.infrastructure.event.external.service.serialization.
import java.util.Collection;
import java.util.List;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.avro.generic.GenericContainer;
import org.apache.commons.collections4.CollectionUtils;
@@ -86,9 +85,10 @@ public class LoanBusinessEventSerializer implements BusinessEventSerializer {
List<LoanInstallmentDelinquencyBucketDataV1> installmentsDelinquencyData = installmentLevelDelinquencyEventProducer
.calculateInstallmentLevelDelinquencyData(event.get(), data.getCurrency());
Set<LoanTermVariations> activeLoanTermVariations = event.get().getActiveLoanTermVariations();
if (activeLoanTermVariations != null) {
data.setEmiAmountVariations(activeLoanTermVariations.stream().map(LoanTermVariations::toData).toList());
List<LoanTermVariations> activeLoanTermVariations = event.get().getActiveLoanTermVariations();
if (!activeLoanTermVariations.isEmpty()) {
data.setLoanTermVariations(activeLoanTermVariations.stream().map(LoanTermVariations::toData).toList());
}
LoanAccountDataV1 result = mapper.map(data);
@@ -123,7 +123,7 @@ public class JobExecutionRepository {
public Long getNotCompletedPartitionsCount(Long jobExecutionId, String partitionerStepName) {
return namedParameterJdbcTemplate.queryForObject("""
SELECT COUNT(bse.STEP_EXECUTION_ID)
SELECT COUNT(BSE.STEP_EXECUTION_ID)
FROM BATCH_STEP_EXECUTION BSE
WHERE
BSE.JOB_EXECUTION_ID = :jobExecutionId
@@ -146,6 +146,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository;
import org.apache.fineract.portfolio.loanaccount.service.GLIMAccountInfoReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -287,6 +288,7 @@ public class LoansApiResource {
private final SqlValidator sqlValidator;
private final LoanSummaryBalancesRepository loanSummaryBalancesRepository;
private final ClientReadPlatformService clientReadPlatformService;
private final LoanTermVariationsRepository loanTermVariationsRepository;
/*
* This template API is used for loan approval, ideally this should be invoked on loan that are pending for
@@ -897,7 +899,8 @@ public class LoansApiResource {
Collection<NoteData> notes = null;
PortfolioAccountData linkedAccount = null;
Collection<DisbursementData> disbursementData = null;
Collection<LoanTermVariationsData> emiAmountVariations = null;
List<LoanTermVariationsData> emiAmountVariations = null;
List<LoanTermVariationsData> loanTermVariations = null;
Collection<LoanCollateralResponseData> loanCollateralManagements;
Collection<LoanCollateralManagementData> loanCollateralManagementData = new ArrayList<>();
CollectionData collectionData = this.delinquencyReadPlatformService.calculateLoanCollectionData(resolvedLoanId);
@@ -911,7 +914,8 @@ public class LoansApiResource {
DataTableApiConstant.transactionsAssociateParamName, DataTableApiConstant.chargesAssociateParamName,
DataTableApiConstant.guarantorsAssociateParamName, DataTableApiConstant.collateralAssociateParamName,
DataTableApiConstant.notesAssociateParamName, DataTableApiConstant.linkedAccountAssociateParamName,
DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName));
DataTableApiConstant.multiDisburseDetailsAssociateParamName, DataTableApiConstant.collectionAssociateParamName,
DataTableApiConstant.loanTermVariationsAssociateParamName));
}
ApiParameterHelper.excludeAssociationsForResponseIfProvided(exclude, associationParameters);
@@ -938,10 +942,15 @@ public class LoansApiResource {
if (associationParameters.contains(DataTableApiConstant.emiAmountVariationsAssociateParamName)
|| associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.emiAmountVariationsAssociateParamName);
emiAmountVariations = this.loanReadPlatformService.retrieveLoanTermVariations(resolvedLoanId,
emiAmountVariations = this.loanTermVariationsRepository.findLoanTermVariationsByLoanIdAndTermType(resolvedLoanId,
LoanTermVariationType.EMI_AMOUNT.getValue());
}
if (associationParameters.contains(DataTableApiConstant.loanTermVariationsAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.loanTermVariationsAssociateParamName);
loanTermVariations = this.loanTermVariationsRepository.findLoanTermVariationsByLoanId(resolvedLoanId);
}
if (associationParameters.contains(DataTableApiConstant.repaymentScheduleAssociateParamName)) {
mandatoryResponseParameters.add(DataTableApiConstant.repaymentScheduleAssociateParamName);
final RepaymentScheduleRelatedLoanData repaymentScheduleRelatedData = new RepaymentScheduleRelatedLoanData(
@@ -1118,7 +1127,8 @@ public class LoansApiResource {
interestCalculationPeriodTypeOptions, fundOptions, chargeOptions, chargeTemplate, allowedLoanOfficers, loanPurposeOptions,
loanCollateralOptions, calendarOptions, notes, accountLinkingOptions, linkedAccount, disbursementData, emiAmountVariations,
overdueCharges, paidInAdvanceTemplate, interestRatesPeriods, clientActiveLoanOptions, rates, isRatesEnabled, collectionData,
LoanScheduleType.getValuesAsEnumOptionDataList(), LoanScheduleProcessingType.getValuesAsEnumOptionDataList());
LoanScheduleType.getValuesAsEnumOptionDataList(), LoanScheduleProcessingType.getValuesAsEnumOptionDataList(),
loanTermVariations);
final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
mandatoryResponseParameters);
@@ -432,11 +432,6 @@ final class LoansApiResourceSwagger {
private GetLoansLoanIdSummary() {}
static final class GetLoansLoanIdEmiVariations {
private GetLoansLoanIdEmiVariations() {}
}
static final class GetLoansLoanIdLinkedAccount {
private GetLoansLoanIdLinkedAccount() {}
@@ -606,7 +601,6 @@ final class LoansApiResourceSwagger {
public Double maxOutstandingLoanBalance;
@Schema(example = "false")
public Boolean canDisburse;
public Set<GetLoansLoanIdEmiVariations> emiAmountVariations;
@Schema(example = "true")
public Boolean inArrears;
@Schema(example = "false")
@@ -1072,6 +1066,38 @@ final class LoansApiResourceSwagger {
}
static final class GetLoansLoanIdLoanTermVariations {
private GetLoansLoanIdLoanTermVariations() {}
static final class GetLoansLoanIdLoanTermEnumData {
private GetLoansLoanIdLoanTermEnumData() {}
@Schema(example = "1")
public Long id;
@Schema(example = "loanTermType.emiAmount")
public String code;
@Schema(example = "emiAmount")
public String value;
}
@Schema(example = "1")
public Long id;
@Schema(description = "Enum option data")
public GetLoansLoanIdLoanTermEnumData termType;
@Schema(example = "[2024, 1, 1]")
public LocalDate termVariationApplicableFrom;
@Schema(example = "200.000000")
public Double decimalValue;
@Schema(example = "[2024, 1, 1]")
public LocalDate dateValue;
@Schema(example = "false")
public boolean isSpecificToInstallment;
@Schema(example = "false")
public boolean isProcessed;
}
@Schema(example = "1")
public Long id;
@Schema(example = "95174ff9-1a75-4d72-a413-6f9b1cb988b7")
@@ -1172,6 +1198,10 @@ final class LoansApiResourceSwagger {
public EnumOptionData loanScheduleType;
@Schema(example = "HORIZONTAL")
public EnumOptionData loanScheduleProcessingType;
@Schema(description = "List of GetLoansLoanIdLoanTermVariations")
public List<GetLoansLoanIdLoanTermVariations> emiAmountVariations;
@Schema(description = "List of GetLoansLoanIdLoanTermVariations")
public List<GetLoansLoanIdLoanTermVariations> loanTermVariations;
}
@Schema(description = "GetLoansResponse")
@@ -1267,6 +1297,8 @@ final class LoansApiResourceSwagger {
public BigDecimal disbursedAmountPercentageForDownPayment;
@Schema(example = "false")
public Boolean enableAutoRepaymentForDownPayment;
@Schema(example = "10.00")
public BigDecimal fixedEmiAmount;
public List<PostLoansRequestChargeData> charges;
@@ -1588,6 +1620,10 @@ final class LoansApiResourceSwagger {
public String withdrawnOnDate;
@Schema(description = "List of PostLoansLoanIdDisbursementData")
public List<PostLoansLoanIdDisbursementData> disbursementData;
@Schema(example = "500.00")
public BigDecimal fixedEmiAmount;
@Schema(example = "28 July 2022")
public String adjustRepaymentDate;
}
@Schema(description = "PostLoansLoanIdResponse")
@@ -194,6 +194,7 @@ public class LoanAccountData {
private Boolean canDisburse;
private Collection<LoanTermVariationsData> emiAmountVariations;
private Collection<LoanTermVariationsData> loanTermVariations;
private Collection<LoanAccountSummaryData> clientActiveLoanOptions;
private Boolean canUseForTopup;
// TODO: avoid prefix "is"
@@ -521,7 +522,7 @@ public class LoanAccountData {
final PaidInAdvanceData paidInAdvance, Collection<InterestRatePeriodData> interestRatesPeriods,
final Collection<LoanAccountSummaryData> clientActiveLoanOptions, final List<RateData> rates, final Boolean isRatesEnabled,
final CollectionData delinquent, final List<EnumOptionData> loanScheduleTypeOptions,
final List<EnumOptionData> loanScheduleProcessingTypeOptions) {
final List<EnumOptionData> loanScheduleProcessingTypeOptions, final List<LoanTermVariationsData> loanTermVariations) {
// TODO: why are these variables 'calendarData', 'chargeTemplate' never used (see original private constructor)
@@ -541,7 +542,7 @@ public class LoanAccountData {
.setOverdueCharges(overdueCharges).setPaidInAdvance(paidInAdvance).setInterestRatesPeriods(interestRatesPeriods)
.setClientActiveLoanOptions(clientActiveLoanOptions).setRates(rates).setIsRatesEnabled(isRatesEnabled)
.setDelinquent(delinquent).setLoanScheduleTypeOptions(loanScheduleTypeOptions)
.setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions);
.setLoanScheduleProcessingTypeOptions(loanScheduleProcessingTypeOptions).setLoanTermVariations(loanTermVariations);
}
public LoanAccountData associationsAndTemplate(final Collection<LoanProductData> productOptions,
@@ -370,9 +370,9 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
final LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
LocalDate rescheduleFromDate = null;
Set<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
List<LoanTermVariations> activeLoanTermVariations = loan.getActiveLoanTermVariations();
LoanTermVariations dueDateVariationInCurrentRequest = loanRescheduleRequest.getDueDateTermVariationIfExists();
if (dueDateVariationInCurrentRequest != null && activeLoanTermVariations != null) {
if (dueDateVariationInCurrentRequest != null && !activeLoanTermVariations.isEmpty()) {
LocalDate fromScheduleDate = dueDateVariationInCurrentRequest.fetchTermApplicaDate();
LocalDate currentScheduleDate = fromScheduleDate;
LocalDate modifiedScheduleDate = dueDateVariationInCurrentRequest.fetchDateValue();
@@ -32,7 +32,6 @@ 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.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.PaidInAdvanceData;
import org.apache.fineract.portfolio.loanaccount.data.RepaymentScheduleRelatedLoanData;
@@ -99,8 +98,6 @@ public interface LoanReadPlatformService {
DisbursementData retrieveLoanDisbursementDetail(Long loanId, Long disbursementId);
Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType);
Collection<LoanScheduleAccrualData> retriveScheduleAccrualData();
LoanTransactionData retrieveRecoveryPaymentTemplate(Long loanId);
@@ -100,7 +100,6 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanRepaymentScheduleInsta
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
import org.apache.fineract.portfolio.loanaccount.data.LoanStatusEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanSummaryData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionEnumData;
import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionRelationData;
@@ -114,7 +113,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTra
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSubStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
@@ -1740,33 +1738,6 @@ public class LoanReadPlatformServiceImpl implements LoanReadPlatformService, Loa
return this.jdbcTemplate.queryForObject(sql, rm, loanId, disbursementId); // NOSONAR
}
@Override
public Collection<LoanTermVariationsData> retrieveLoanTermVariations(Long loanId, Integer termType) {
final LoanTermVariationsMapper rm = new LoanTermVariationsMapper();
final String sql = "select " + rm.schema() + " where tv.loan_id=? and tv.term_type=?";
return this.jdbcTemplate.query(sql, rm, loanId, termType); // NOSONAR
}
private static final class LoanTermVariationsMapper implements RowMapper<LoanTermVariationsData> {
public String schema() {
return "tv.id as id,tv.applicable_date as variationApplicableFrom,tv.decimal_value as decimalValue, tv.date_value as dateValue, tv.is_specific_to_installment as isSpecificToInstallment "
+ "from m_loan_term_variations tv";
}
@Override
public LoanTermVariationsData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException {
final Long id = rs.getLong("id");
final LocalDate variationApplicableFrom = JdbcSupport.getLocalDate(rs, "variationApplicableFrom");
final BigDecimal decimalValue = rs.getBigDecimal("decimalValue");
final LocalDate dateValue = JdbcSupport.getLocalDate(rs, "dateValue");
final boolean isSpecificToInstallment = rs.getBoolean("isSpecificToInstallment");
return new LoanTermVariationsData(id, LoanEnumerations.loanVariationType(LoanTermVariationType.EMI_AMOUNT),
variationApplicableFrom, decimalValue, dateValue, isSpecificToInstallment);
}
}
@Override
public Collection<LoanScheduleAccrualData> retriveScheduleAccrualData() {
final String chargeAccrualDateCriteria = configurationDomainService.getAccrualDateConfigForCharge();