FINERACT-2065: Schedule handling for fixed length configuration
This commit is contained in:
committed by
Adam Saghy
parent
6458094a5e
commit
b64b4b7105
+11
-6
@@ -34,7 +34,6 @@ import org.apache.fineract.portfolio.calendar.domain.CalendarHistory;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@@ -46,16 +45,16 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
|
||||
final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();
|
||||
|
||||
RepaymentStartDateType repaymentStartDateType = loanApplicationTerms.getRepaymentStartDateType();
|
||||
|
||||
LocalDate lastRepaymentDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(repaymentStartDateType)
|
||||
? loanApplicationTerms.getExpectedDisbursementDate()
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
LocalDate lastRepaymentDate = loanApplicationTerms.getRepaymentStartDate();
|
||||
boolean isFirstRepayment = true;
|
||||
for (int repaymentPeriod = 1; repaymentPeriod <= numberOfRepayments; repaymentPeriod++) {
|
||||
lastRepaymentDate = generateNextRepaymentDate(lastRepaymentDate, loanApplicationTerms, isFirstRepayment);
|
||||
isFirstRepayment = false;
|
||||
}
|
||||
if (loanApplicationTerms.getFixedLength() != null
|
||||
&& loanApplicationTerms.calculateMaxDateForFixedLength().compareTo(lastRepaymentDate) != 0) {
|
||||
lastRepaymentDate = loanApplicationTerms.calculateMaxDateForFixedLength();
|
||||
}
|
||||
lastRepaymentDate = adjustRepaymentDate(lastRepaymentDate, loanApplicationTerms, holidayDetailDTO).getChangedScheduleDate();
|
||||
return lastRepaymentDate;
|
||||
}
|
||||
@@ -104,6 +103,12 @@ public class DefaultScheduledDateGenerator implements ScheduledDateGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
final LocalDate maxDateForFixedLength = loanApplicationTerms.calculateMaxDateForFixedLength();
|
||||
// Fixed Length validation
|
||||
if (maxDateForFixedLength != null && DateUtils.isAfter(dueRepaymentPeriodDate, maxDateForFixedLength)) {
|
||||
dueRepaymentPeriodDate = maxDateForFixedLength;
|
||||
}
|
||||
|
||||
return dueRepaymentPeriodDate;
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
|
||||
@@ -29,6 +30,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
|
||||
/**
|
||||
* Domain representation of a Loan Schedule Repayment Period (not used for persistence)
|
||||
*/
|
||||
@Getter
|
||||
public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
|
||||
|
||||
private final int periodNumber;
|
||||
|
||||
+2
-1
@@ -70,7 +70,8 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
|
||||
LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
|
||||
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, LoanApiConstants.isTopup, LoanApiConstants.loanIdToClose,
|
||||
LoanApiConstants.datatables, LoanApiConstants.isEqualAmortizationParam, LoanProductConstants.RATES_PARAM_NAME,
|
||||
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
|
||||
LoanApiConstants.daysInYearTypeParameterName, LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
|
||||
LoanProductConstants.FIXED_LENGTH));
|
||||
|
||||
private final FromJsonHelper fromApiJsonHelper;
|
||||
|
||||
|
||||
+4
-1
@@ -196,6 +196,7 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
private final LoanRepository loanRepository;
|
||||
private final GSIMReadPlatformService gsimReadPlatformService;
|
||||
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
|
||||
private final LoanProductDataValidator loanProductDataValidator;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
@@ -253,6 +254,9 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
}
|
||||
|
||||
final Loan newLoanApplication = this.loanAssembler.assembleFrom(command);
|
||||
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson());
|
||||
loanProductDataValidator.fixedLengthValidations(newLoanApplication.getTransactionProcessingStrategyCode(), loanApplicationTerms,
|
||||
this.fromJsonHelper.parse(command.json()), baseDataValidator);
|
||||
|
||||
checkForProductMixRestrictions(newLoanApplication);
|
||||
|
||||
@@ -438,7 +442,6 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
|
||||
CalendarEntityType.LOANS.getValue());
|
||||
this.calendarInstanceRepository.save(calendarInstance);
|
||||
} else {
|
||||
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(command.parsedJson());
|
||||
final Integer repaymentFrequencyNthDayType = command.integerValueOfParameterNamed("repaymentFrequencyNthDayType");
|
||||
if (loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.MONTHS
|
||||
&& repaymentFrequencyNthDayType != null) {
|
||||
|
||||
+2
-2
@@ -231,7 +231,7 @@ public class LoanAccountConfiguration {
|
||||
|
||||
RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
|
||||
GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
|
||||
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
|
||||
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator) {
|
||||
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator,
|
||||
loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository,
|
||||
calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler,
|
||||
@@ -241,7 +241,7 @@ public class LoanAccountConfiguration {
|
||||
configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService,
|
||||
entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository,
|
||||
fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService,
|
||||
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine);
|
||||
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator);
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
+60
-5
@@ -39,6 +39,7 @@ import org.apache.fineract.accounting.common.AccountingValidations;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
||||
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
|
||||
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||
@@ -47,6 +48,7 @@ import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
|
||||
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
|
||||
@@ -399,6 +401,7 @@ public final class LoanProductDataValidator {
|
||||
}
|
||||
|
||||
// interest rates
|
||||
BigDecimal interestRatePerPeriod = null;
|
||||
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)
|
||||
&& this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
|
||||
if (isEqualAmortization) {
|
||||
@@ -519,8 +522,7 @@ public final class LoanProductDataValidator {
|
||||
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
|
||||
}
|
||||
|
||||
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD,
|
||||
element);
|
||||
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
|
||||
baseDataValidator.reset().parameter(INTEREST_RATE_PER_PERIOD).value(interestRatePerPeriod).notNull().zeroOrPositiveAmount();
|
||||
|
||||
final String minInterestRatePerPeriodParameterName = MIN_INTEREST_RATE_PER_PERIOD;
|
||||
@@ -564,6 +566,10 @@ public final class LoanProductDataValidator {
|
||||
4);
|
||||
}
|
||||
|
||||
// Fixed Length validation
|
||||
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
|
||||
baseDataValidator);
|
||||
|
||||
// Guarantee Funds
|
||||
Boolean holdGuaranteeFunds = false;
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
|
||||
@@ -1238,13 +1244,15 @@ public final class LoanProductDataValidator {
|
||||
.integerGreaterThanZero();
|
||||
}
|
||||
|
||||
Integer numberOfRepayments = loanProduct.getNumberOfRepayments();
|
||||
if (this.fromApiJsonHelper.parameterExists(NUMBER_OF_REPAYMENTS, element)) {
|
||||
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(NUMBER_OF_REPAYMENTS, element);
|
||||
numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(NUMBER_OF_REPAYMENTS, element);
|
||||
baseDataValidator.reset().parameter(NUMBER_OF_REPAYMENTS).value(numberOfRepayments).notNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
Integer repaymentEvery = loanProduct.getLoanProductRelatedDetail().getRepayEvery();
|
||||
if (this.fromApiJsonHelper.parameterExists(REPAYMENT_EVERY, element)) {
|
||||
final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(REPAYMENT_EVERY, element);
|
||||
repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(REPAYMENT_EVERY, element);
|
||||
baseDataValidator.reset().parameter(REPAYMENT_EVERY).value(repaymentEvery).notNull().integerGreaterThanZero();
|
||||
}
|
||||
|
||||
@@ -1364,6 +1372,7 @@ public final class LoanProductDataValidator {
|
||||
}
|
||||
|
||||
// interest rates
|
||||
BigDecimal interestRatePerPeriod = null;
|
||||
boolean isLinkedToFloatingInterestRates = loanProduct.isLinkedToFloatingInterestRate();
|
||||
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
|
||||
isLinkedToFloatingInterestRates = this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element);
|
||||
@@ -1531,7 +1540,7 @@ public final class LoanProductDataValidator {
|
||||
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
|
||||
.zeroOrPositiveAmount();
|
||||
|
||||
BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
|
||||
interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
|
||||
if (this.fromApiJsonHelper.parameterExists(INTEREST_RATE_PER_PERIOD, element)) {
|
||||
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
|
||||
}
|
||||
@@ -1546,6 +1555,10 @@ public final class LoanProductDataValidator {
|
||||
4);
|
||||
}
|
||||
|
||||
// Fixed Length validation
|
||||
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
|
||||
baseDataValidator);
|
||||
|
||||
// Guarantee Funds
|
||||
Boolean holdGuaranteeFunds = loanProduct.isHoldGuaranteeFundsEnabled();
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.holdGuaranteeFundsParamName, element)) {
|
||||
@@ -2394,4 +2407,46 @@ public final class LoanProductDataValidator {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final LoanApplicationTerms loanApplicationTerms,
|
||||
final JsonElement element, final DataValidatorBuilder baseDataValidator) {
|
||||
fixedLengthValidations(transactionProcessingStrategyCode, loanApplicationTerms.getAnnualNominalInterestRate(),
|
||||
loanApplicationTerms.getNumberOfRepayments(), loanApplicationTerms.getRepaymentEvery(), element, baseDataValidator);
|
||||
}
|
||||
|
||||
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final BigDecimal interestRatePerPeriod,
|
||||
final Integer numberOfRepayments, final Integer repayEvery, final JsonElement element,
|
||||
final DataValidatorBuilder baseDataValidator) {
|
||||
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) {
|
||||
final JsonObject topLevelJsonElement = element.getAsJsonObject();
|
||||
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
|
||||
final Integer fixedLength = this.fromApiJsonHelper.extractIntegerNamed(LoanProductConstants.FIXED_LENGTH, element, locale);
|
||||
baseDataValidator.reset().parameter(LoanProductConstants.FIXED_LENGTH).value(fixedLength).ignoreIfNull()
|
||||
.integerGreaterThanZero();
|
||||
|
||||
if (fixedLength != null) {
|
||||
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
|
||||
.equals(transactionProcessingStrategyCode)) {
|
||||
final String errorMsg = "Fixed Length configuration is only allowed with "
|
||||
+ AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + " stratefy";
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.advanced.payment.allocation",
|
||||
errorMsg, AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY);
|
||||
}
|
||||
|
||||
if (interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final String errorMsg = "Fixed Length configuration is only allowed for zero interest products";
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg,
|
||||
interestRatePerPeriod);
|
||||
}
|
||||
|
||||
final Integer valueToCompare = ((numberOfRepayments - 1) * repayEvery) + 1;
|
||||
if (fixedLength.compareTo(valueToCompare) < 0) {
|
||||
final String errorMsg = "Wrong configuration between Number Of Repayments: " + numberOfRepayments + " * " + repayEvery
|
||||
+ " and Fixed Length: " + fixedLength + " values";
|
||||
throw new GeneralPlatformDomainRuleException("error.msg.number.repayments.and.fixed.length.configuration.not.valid",
|
||||
errorMsg, numberOfRepayments, repayEvery, valueToCompare, fixedLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user