FINERACT-2090: Restructure Loan creation and modification

This commit is contained in:
Adam Saghy
2024-06-06 23:09:50 +02:00
parent a078cfd8c5
commit 92e291a9e4
34 changed files with 2458 additions and 2486 deletions
@@ -175,7 +175,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
List<EntityDatatableChecks> tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndProduct(entityName,
statusCode, productId);
if (tableRequiredBeforAction == null || tableRequiredBeforAction.size() < 1) {
if (tableRequiredBeforAction == null || tableRequiredBeforAction.isEmpty()) {
tableRequiredBeforAction = entityDatatableChecksRepository.findByEntityStatusAndNoProduct(entityName, statusCode);
}
if (tableRequiredBeforAction != null) {
@@ -190,7 +190,7 @@ public class EntityDatatableChecksWritePlatformServiceImpl implements EntityData
reqDatatables.add(datatableName);
}
}
if (reqDatatables.size() > 0) {
if (!reqDatatables.isEmpty()) {
throw new DatatableEntryRequiredException(reqDatatables.toString());
}
}
@@ -81,7 +81,6 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferStandingInstr
import org.apache.fineract.portfolio.account.domain.AccountTransferTransaction;
import org.apache.fineract.portfolio.account.domain.StandingInstructionRepository;
import org.apache.fineract.portfolio.account.domain.StandingInstructionStatus;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
@@ -250,7 +249,7 @@ public class LoanAccountDomainServiceJpa implements LoanAccountDomainService {
// changes to closed
disableStandingInstructionsLinkedToClosedLoan(loan);
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
// Mark Post Dated Check as paid.
final Set<LoanTransactionToRepaymentScheduleMapping> loanTransactionToRepaymentScheduleMappings = newRepaymentTransaction
.getLoanTransactionToRepaymentScheduleMappings();
@@ -34,8 +34,10 @@ import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
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.PlatformApiDataValidationException;
@@ -100,6 +102,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.serialization.VariableLoanScheduleFromApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementDetailsAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.AmortizationMethod;
@@ -115,10 +118,10 @@ import org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyTy
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.service.LoanEnumerations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class LoanScheduleAssembler {
private final FromJsonHelper fromApiJsonHelper;
@@ -137,35 +140,7 @@ public class LoanScheduleAssembler {
private final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator;
private final CalendarInstanceRepository calendarInstanceRepository;
private final LoanUtilService loanUtilService;
@Autowired
public LoanScheduleAssembler(final FromJsonHelper fromApiJsonHelper, final LoanProductRepository loanProductRepository,
final ApplicationCurrencyRepositoryWrapper applicationCurrencyRepository,
final LoanScheduleGeneratorFactory loanScheduleFactory, final AprCalculator aprCalculator,
final LoanChargeAssembler loanChargeAssembler, final CalendarRepository calendarRepository,
final HolidayRepository holidayRepository, final ConfigurationDomainService configurationDomainService,
final ClientRepositoryWrapper clientRepository, final GroupRepositoryWrapper groupRepository,
final WorkingDaysRepositoryWrapper workingDaysRepository,
final FloatingRatesReadPlatformService floatingRatesReadPlatformService,
final VariableLoanScheduleFromApiJsonValidator variableLoanScheduleFromApiJsonValidator,
final CalendarInstanceRepository calendarInstanceRepository, final LoanUtilService loanUtilService) {
this.fromApiJsonHelper = fromApiJsonHelper;
this.loanProductRepository = loanProductRepository;
this.applicationCurrencyRepository = applicationCurrencyRepository;
this.loanScheduleFactory = loanScheduleFactory;
this.aprCalculator = aprCalculator;
this.loanChargeAssembler = loanChargeAssembler;
this.calendarRepository = calendarRepository;
this.holidayRepository = holidayRepository;
this.configurationDomainService = configurationDomainService;
this.clientRepository = clientRepository;
this.groupRepository = groupRepository;
this.workingDaysRepository = workingDaysRepository;
this.floatingRatesReadPlatformService = floatingRatesReadPlatformService;
this.variableLoanScheduleFromApiJsonValidator = variableLoanScheduleFromApiJsonValidator;
this.calendarInstanceRepository = calendarInstanceRepository;
this.loanUtilService = loanUtilService;
}
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
public LoanApplicationTerms assembleLoanTerms(final JsonElement element) {
final Long loanProductId = this.fromApiJsonHelper.extractLongNamed("productId", element);
@@ -177,6 +152,15 @@ public class LoanScheduleAssembler {
private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) {
final Boolean allowOverridingAmortization = loanProduct.getLoanConfigurableAttributes().getAmortizationBoolean();
final Boolean allowOverridingArrearsTolerance = loanProduct.getLoanConfigurableAttributes().getArrearsToleranceBoolean();
final Boolean allowOverridingGraceOnArrearsAging = loanProduct.getLoanConfigurableAttributes().getGraceOnArrearsAgingBoolean();
final Boolean allowOverridingInterestCalcPeriod = loanProduct.getLoanConfigurableAttributes().getInterestCalcPeriodBoolean();
final Boolean allowOverridingInterestMethod = loanProduct.getLoanConfigurableAttributes().getInterestMethodBoolean();
final Boolean allowOverridingGraceOnPrincipalAndInterestPayment = loanProduct.getLoanConfigurableAttributes()
.getGraceOnPrincipalAndInterestPaymentBoolean();
final Boolean allowOverridingRepaymentEvery = loanProduct.getLoanConfigurableAttributes().getRepaymentEveryBoolean();
final MonetaryCurrency currency = loanProduct.getCurrency();
final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository.findOneWithNotFoundDetection(currency);
@@ -186,7 +170,9 @@ public class LoanScheduleAssembler {
final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType);
final Integer numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("numberOfRepayments", element);
final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element);
final Integer repaymentEvery = allowOverridingRepaymentEvery
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element)
: loanProduct.getLoanProductRelatedDetail().getRepayEvery();
final Integer repaymentFrequencyType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyType", element);
final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType.fromInt(repaymentFrequencyType);
final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element);
@@ -194,7 +180,8 @@ public class LoanScheduleAssembler {
final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek);
final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element);
final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType);
final AmortizationMethod amortizationMethod = allowOverridingAmortization ? AmortizationMethod.fromInt(amortizationType)
: loanProduct.getLoanProductRelatedDetail().getAmortizationMethod();
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
@@ -206,12 +193,14 @@ public class LoanScheduleAssembler {
// interest terms
final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element);
final InterestMethod interestMethod = InterestMethod.fromInt(interestType);
final InterestMethod interestMethod = allowOverridingInterestMethod ? InterestMethod.fromInt(interestType)
: loanProduct.getLoanProductRelatedDetail().getInterestMethod();
final Integer interestCalculationPeriodType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestCalculationPeriodType",
element);
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod
.fromInt(interestCalculationPeriodType);
final InterestCalculationPeriodMethod interestCalculationPeriodMethod = allowOverridingInterestCalcPeriod
? InterestCalculationPeriodMethod.fromInt(interestCalculationPeriodType)
: loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod();
Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, element);
if (allowPartialPeriodInterestCalcualtion == null) {
@@ -322,23 +311,29 @@ public class LoanScheduleAssembler {
}
// grace details
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element);
final Integer graceOnPrincipalPayment = allowOverridingGraceOnPrincipalAndInterestPayment
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element);
final Integer graceOnInterestPayment = allowOverridingGraceOnPrincipalAndInterestPayment
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestPayment", element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("graceOnInterestCharged", element);
final LocalDate interestChargedFromDate = this.fromApiJsonHelper.extractLocalDateNamed("interestChargedFromDate", element);
final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService
.isInterestChargedFromDateSameAsDisbursementDate();
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element);
final Integer graceOnArrearsAgeing = allowOverridingGraceOnArrearsAging
? this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element)
: loanProduct.getLoanProductRelatedDetail().getGraceOnArrearsAgeing();
// other
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("inArrearsTolerance", element);
final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance);
final Money inArrearsToleranceMoney = allowOverridingArrearsTolerance ? Money.of(currency, inArrearsTolerance)
: loanProduct.getLoanProductRelatedDetail().getInArrearsTolerance();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element);
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
@@ -638,8 +633,8 @@ public class LoanScheduleAssembler {
}
}
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element) {
final LoanApplicationTerms loanApplicationTerms = assembleLoanTerms(element);
public LoanProductRelatedDetail assembleLoanProductRelatedDetail(final JsonElement element, final LoanProduct loanProduct) {
final LoanApplicationTerms loanApplicationTerms = assembleLoanApplicationTermsFrom(element, loanProduct);
return loanApplicationTerms.toLoanProductRelatedDetail();
}
@@ -671,7 +666,8 @@ public class LoanScheduleAssembler {
validateDisbursementDateIsOnNonWorkingDay(loanApplicationTerms.getExpectedDisbursementDate(), workingDays);
validateDisbursementDateIsOnHoliday(loanApplicationTerms.getExpectedDisbursementDate(), isHolidayEnabled, holidays);
List<LoanDisbursementDetails> loanDisbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
List<LoanDisbursementDetails> loanDisbursementDetails = this.loanDisbursementDetailsAssembler
.fetchDisbursementData(element.getAsJsonObject());
return assembleLoanScheduleFrom(loanApplicationTerms, isHolidayEnabled, holidays, workingDays, element, loanDisbursementDetails);
}
@@ -769,7 +765,7 @@ public class LoanScheduleAssembler {
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LocalDate> dueDates = new TreeSet<>();
LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (graceOnPrincipal == null) {
graceOnPrincipal = 0;
}
@@ -1160,4 +1156,232 @@ public class LoanScheduleAssembler {
minimumDaysBetweenDisbursalAndFirstRepayment);
}
}
public void updateProductRelatedDetails(LoanProductRelatedDetail productRelatedDetail, Loan loan) {
final Boolean amortization = loan.loanProduct().getLoanConfigurableAttributes().getAmortizationBoolean();
final Boolean arrearsTolerance = loan.loanProduct().getLoanConfigurableAttributes().getArrearsToleranceBoolean();
final Boolean graceOnArrearsAging = loan.loanProduct().getLoanConfigurableAttributes().getGraceOnArrearsAgingBoolean();
final Boolean interestCalcPeriod = loan.loanProduct().getLoanConfigurableAttributes().getInterestCalcPeriodBoolean();
final Boolean interestMethod = loan.loanProduct().getLoanConfigurableAttributes().getInterestMethodBoolean();
final Boolean graceOnPrincipalAndInterestPayment = loan.loanProduct().getLoanConfigurableAttributes()
.getGraceOnPrincipalAndInterestPaymentBoolean();
final Boolean repaymentEvery = loan.loanProduct().getLoanConfigurableAttributes().getRepaymentEveryBoolean();
if (!amortization) {
productRelatedDetail.setAmortizationMethod(loan.loanProduct().getLoanProductRelatedDetail().getAmortizationMethod());
}
if (!arrearsTolerance) {
productRelatedDetail
.setInArrearsTolerance(loan.loanProduct().getLoanProductRelatedDetail().getInArrearsTolerance().getAmount());
}
if (!graceOnArrearsAging) {
productRelatedDetail.setGraceOnArrearsAgeing(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnArrearsAgeing());
}
if (!interestCalcPeriod) {
productRelatedDetail.setInterestCalculationPeriodMethod(
loan.loanProduct().getLoanProductRelatedDetail().getInterestCalculationPeriodMethod());
}
if (!interestMethod) {
productRelatedDetail.setInterestMethod(loan.loanProduct().getLoanProductRelatedDetail().getInterestMethod());
}
if (!graceOnPrincipalAndInterestPayment) {
productRelatedDetail.setGraceOnInterestPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnInterestPayment());
productRelatedDetail.setGraceOnPrincipalPayment(loan.loanProduct().getLoanProductRelatedDetail().getGraceOnPrincipalPayment());
}
if (!repaymentEvery) {
productRelatedDetail.setRepayEvery(loan.loanProduct().getLoanProductRelatedDetail().getRepayEvery());
}
}
public void updateLoanApplicationAttributes(JsonCommand command, Loan loan, Map<String, Object> changes) {
final String localeAsInput = command.locale();
final String principalParamName = "principal";
LoanProductRelatedDetail loanProductRelatedDetail = loan.getLoanRepaymentScheduleDetail();
if (command.isChangeInBigDecimalParameterNamed(principalParamName, loanProductRelatedDetail.getPrincipal().getAmount())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(principalParamName);
changes.put(principalParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setPrincipal(newValue);
}
final String repaymentEveryParamName = "repaymentEvery";
if (command.isChangeInIntegerParameterNamed(repaymentEveryParamName, loanProductRelatedDetail.getRepayEvery())) {
final Integer newValue = command.integerValueOfParameterNamed(repaymentEveryParamName);
changes.put(repaymentEveryParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRepayEvery(newValue);
}
final String repaymentFrequencyTypeParamName = "repaymentFrequencyType";
if (command.isChangeInIntegerParameterNamed(repaymentFrequencyTypeParamName,
loanProductRelatedDetail.getRepaymentPeriodFrequencyType().getValue())) {
Integer newValue = command.integerValueOfParameterNamed(repaymentFrequencyTypeParamName);
changes.put(repaymentFrequencyTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRepaymentPeriodFrequencyType(PeriodFrequencyType.fromInt(newValue));
}
if (PeriodFrequencyType.MONTHS.equals(loanProductRelatedDetail.getRepaymentPeriodFrequencyType())) {
final String repaymentFrequencyNthDayTypeParamName = "repaymentFrequencyNthDayType";
Integer newValue = command.integerValueOfParameterNamed(repaymentFrequencyNthDayTypeParamName);
changes.put(repaymentFrequencyNthDayTypeParamName, newValue);
final String repaymentFrequencyDayOfWeekTypeParamName = "repaymentFrequencyDayOfWeekType";
newValue = command.integerValueOfParameterNamed(repaymentFrequencyDayOfWeekTypeParamName);
changes.put(repaymentFrequencyDayOfWeekTypeParamName, newValue);
changes.put("locale", localeAsInput);
}
final String numberOfRepaymentsParamName = "numberOfRepayments";
if (command.isChangeInIntegerParameterNamed(numberOfRepaymentsParamName, loanProductRelatedDetail.getNumberOfRepayments())) {
final Integer newValue = command.integerValueOfParameterNamed(numberOfRepaymentsParamName);
changes.put(numberOfRepaymentsParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setNumberOfRepayments(newValue);
}
final String amortizationTypeParamName = "amortizationType";
if (command.isChangeInIntegerParameterNamed(amortizationTypeParamName,
loanProductRelatedDetail.getAmortizationMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(amortizationTypeParamName);
changes.put(amortizationTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setAmortizationMethod(AmortizationMethod.fromInt(newValue));
}
final String inArrearsToleranceParamName = "inArrearsTolerance";
if (command.isChangeInBigDecimalParameterNamed(inArrearsToleranceParamName,
loanProductRelatedDetail.getInArrearsTolerance().getAmount())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(inArrearsToleranceParamName);
changes.put(inArrearsToleranceParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInArrearsTolerance(newValue);
}
final String interestRatePerPeriodParamName = "interestRatePerPeriod";
if (command.isChangeInBigDecimalParameterNamed(interestRatePerPeriodParamName,
loanProductRelatedDetail.getNominalInterestRatePerPeriod())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(interestRatePerPeriodParamName);
changes.put(interestRatePerPeriodParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setNominalInterestRatePerPeriod(newValue);
loanProductRelatedDetail.updateInterestRateDerivedFields(aprCalculator);
}
final String interestRateFrequencyTypeParamName = "interestRateFrequencyType";
final int interestPeriodFrequencyType = loanProductRelatedDetail.getInterestPeriodFrequencyType() == null
? PeriodFrequencyType.INVALID.getValue()
: loanProductRelatedDetail.getInterestPeriodFrequencyType().getValue();
if (command.isChangeInIntegerParameterNamed(interestRateFrequencyTypeParamName, interestPeriodFrequencyType)) {
final Integer newValue = command.integerValueOfParameterNamed(interestRateFrequencyTypeParamName);
changes.put(interestRateFrequencyTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestPeriodFrequencyType(PeriodFrequencyType.fromInt(newValue));
loanProductRelatedDetail.updateInterestRateDerivedFields(aprCalculator);
}
final String interestTypeParamName = "interestType";
if (command.isChangeInIntegerParameterNamed(interestTypeParamName, loanProductRelatedDetail.getInterestMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(interestTypeParamName);
changes.put(interestTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestMethod(InterestMethod.fromInt(newValue));
}
final String interestCalculationPeriodTypeParamName = "interestCalculationPeriodType";
if (command.isChangeInIntegerParameterNamed(interestCalculationPeriodTypeParamName,
loanProductRelatedDetail.getInterestCalculationPeriodMethod().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(interestCalculationPeriodTypeParamName);
changes.put(interestCalculationPeriodTypeParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setInterestCalculationPeriodMethod(InterestCalculationPeriodMethod.fromInt(newValue));
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
loanProductRelatedDetail.isAllowPartialPeriodInterestCalcualtion())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME);
changes.put(LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME, newValue);
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(newValue);
}
if (loanProductRelatedDetail.getInterestCalculationPeriodMethod().isDaily()) {
loanProductRelatedDetail.setAllowPartialPeriodInterestCalcualtion(false);
}
final String graceOnPrincipalPaymentParamName = "graceOnPrincipalPayment";
if (command.isChangeInIntegerParameterNamed(graceOnPrincipalPaymentParamName,
loanProductRelatedDetail.getGraceOnPrincipalPayment())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnPrincipalPaymentParamName);
changes.put(graceOnPrincipalPaymentParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnPrincipalPayment(newValue);
}
final String recurringMoratoriumOnPrincipalPeriodsParamName = "recurringMoratoriumOnPrincipalPeriods";
if (command.isChangeInIntegerParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName,
loanProductRelatedDetail.getRecurringMoratoriumOnPrincipalPeriods())) {
final Integer newValue = command.integerValueOfParameterNamed(recurringMoratoriumOnPrincipalPeriodsParamName);
changes.put(recurringMoratoriumOnPrincipalPeriodsParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setRecurringMoratoriumOnPrincipalPeriods(newValue);
}
final String graceOnInterestPaymentParamName = "graceOnInterestPayment";
if (command.isChangeInIntegerParameterNamed(graceOnInterestPaymentParamName,
loanProductRelatedDetail.getGraceOnInterestPayment())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestPaymentParamName);
changes.put(graceOnInterestPaymentParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnInterestPayment(newValue);
}
final String graceOnInterestChargedParamName = "graceOnInterestCharged";
if (command.isChangeInIntegerParameterNamed(graceOnInterestChargedParamName,
loanProductRelatedDetail.getGraceOnInterestCharged())) {
final Integer newValue = command.integerValueOfParameterNamed(graceOnInterestChargedParamName);
changes.put(graceOnInterestChargedParamName, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnInterestCharged(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
loanProductRelatedDetail.getGraceOnArrearsAgeing())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME);
changes.put(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setGraceOnArrearsAgeing(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME,
loanProductRelatedDetail.fetchDaysInMonthType().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME);
changes.put(LoanProductConstants.DAYS_IN_MONTH_TYPE_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setDaysInMonthType(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME,
loanProductRelatedDetail.fetchDaysInYearType().getValue())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME);
changes.put(LoanProductConstants.DAYS_IN_YEAR_TYPE_PARAMETER_NAME, newValue);
changes.put("locale", localeAsInput);
loanProductRelatedDetail.setDaysInYearType(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME,
loanProductRelatedDetail.isInterestRecalculationEnabled())) {
final boolean newValue = command
.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME);
changes.put(LoanProductConstants.IS_INTEREST_RECALCULATION_ENABLED_PARAMETER_NAME, newValue);
loanProductRelatedDetail.setInterestRecalculationEnabled(newValue);
}
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM,
loanProductRelatedDetail.isEqualAmortization())) {
final boolean newValue = command.booleanPrimitiveValueOfParameterNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM);
changes.put(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, newValue);
loanProductRelatedDetail.setEqualAmortization(newValue);
}
}
}
@@ -24,80 +24,46 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.organisation.monetary.service.CurrencyReadPlatformService;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.serialization.CalculateLoanScheduleQueryFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanScheduleValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanUtilService;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
@RequiredArgsConstructor
public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleCalculationPlatformService {
private final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer;
private final LoanScheduleValidator fromApiJsonDeserializer;
private final LoanScheduleAssembler loanScheduleAssembler;
private final FromJsonHelper fromJsonHelper;
private final LoanProductRepository loanProductRepository;
private final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer;
private final LoanReadPlatformService loanReadPlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer;
private final LoanAssembler loanAssembler;
private final LoanApplicationValidator loanApiJsonDeserializer;
private final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory;
private final ConfigurationDomainService configurationDomainService;
private final CurrencyReadPlatformService currencyReadPlatformService;
private final LoanUtilService loanUtilService;
@Autowired
public LoanScheduleCalculationPlatformServiceImpl(final CalculateLoanScheduleQueryFromApiJsonHelper fromApiJsonDeserializer,
final LoanScheduleAssembler loanScheduleAssembler, final FromJsonHelper fromJsonHelper,
final LoanProductRepository loanProductRepository, final LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
final LoanReadPlatformService loanReadPlatformService, final LoanApplicationCommandFromApiJsonHelper loanApiJsonDeserializer,
final LoanAssembler loanAssembler,
final LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
final ConfigurationDomainService configurationDomainService, final CurrencyReadPlatformService currencyReadPlatformService,
final LoanUtilService loanUtilService) {
this.fromApiJsonDeserializer = fromApiJsonDeserializer;
this.loanScheduleAssembler = loanScheduleAssembler;
this.fromJsonHelper = fromJsonHelper;
this.loanProductRepository = loanProductRepository;
this.loanProductCommandFromApiJsonDeserializer = loanProductCommandFromApiJsonDeserializer;
this.loanReadPlatformService = loanReadPlatformService;
this.loanApiJsonDeserializer = loanApiJsonDeserializer;
this.loanAssembler = loanAssembler;
this.loanRepaymentScheduleTransactionProcessorFactory = loanRepaymentScheduleTransactionProcessorFactory;
this.configurationDomainService = configurationDomainService;
this.currencyReadPlatformService = currencyReadPlatformService;
this.loanUtilService = loanUtilService;
}
private final LoanRepositoryWrapper loanRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanSummaryWrapper loanSummaryWrapper;
@Override
public LoanScheduleModel calculateLoanSchedule(final JsonQuery query, Boolean validateParams) {
@@ -105,44 +71,19 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
/***
* TODO: Vishwas, this is probably not required, test and remove the same
**/
final Long productId = this.fromJsonHelper.extractLongNamed("productId", query.parsedJson());
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
if (validateParams) {
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
this.loanApiJsonDeserializer.validateForCreate(query.json(), isMeetingMandatoryForJLGLoans, loanProduct);
this.loanApiJsonDeserializer.validateForCreate(query);
}
this.fromApiJsonDeserializer.validate(query.json());
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (loanProduct.isUseBorrowerCycle()) {
final Long clientId = this.fromJsonHelper.extractLongNamed("clientId", query.parsedJson());
final Long groupId = this.fromJsonHelper.extractLongNamed("groupId", query.parsedJson());
Integer cycleNumber = 0;
if (clientId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(clientId, loanProduct.getId());
} else if (groupId != null) {
cycleNumber = this.loanReadPlatformService.retriveLoanCounter(groupId, AccountType.GROUP.getValue(), loanProduct.getId());
}
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct,
cycleNumber);
} else {
this.loanProductCommandFromApiJsonDeserializer.validateMinMaxConstraints(query.parsedJson(), baseDataValidator, loanProduct);
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
return this.loanScheduleAssembler.assembleLoanScheduleFrom(query.parsedJson());
}
@Override
public void updateFutureSchedule(LoanScheduleData loanScheduleData, final Long loanId) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final Loan loan = fetchLoan(loanId);
LocalDate today = DateUtils.getBusinessLocalDate();
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
@@ -210,7 +151,7 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
@Override
@Transactional(readOnly = true)
public LoanScheduleData generateLoanScheduleForVariableInstallmentRequest(Long loanId, final String json) {
final Loan loan = this.loanAssembler.assembleFrom(loanId);
final Loan loan = fetchLoan(loanId);
this.loanScheduleAssembler.assempleVariableScheduleFrom(loan, json);
return constructLoanScheduleData(loan);
}
@@ -262,19 +203,23 @@ public class LoanScheduleCalculationPlatformServiceImpl implements LoanScheduleC
CurrencyData currencyData = this.currencyReadPlatformService.retrieveCurrency(currency.getCode());
LoanScheduleData scheduleData = new LoanScheduleData(currencyData, installmentData,
loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(), principal.getAmount(), principal.getAmount(),
totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
return new LoanScheduleData(currencyData, installmentData, loan.getLoanRepaymentScheduleDetail().getNumberOfRepayments(),
principal.getAmount(), principal.getAmount(), totalInterest.getAmount(), totalCharge.getAmount(), totalPenalty.getAmount(),
principal.plus(totalCharge).plus(totalInterest).plus(totalPenalty).getAmount());
return scheduleData;
}
private LoanApplicationTerms constructLoanApplicationTerms(final Loan loan) {
final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
LoanApplicationTerms loanApplicationTerms = loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
return loanApplicationTerms;
return loan.constructLoanApplicationTerms(scheduleGeneratorDTO);
}
private Loan fetchLoan(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
loanAccount.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
return loanAccount;
}
}
@@ -18,7 +18,9 @@
*/
package org.apache.fineract.portfolio.loanaccount.serialization;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
import java.math.BigDecimal;
@@ -27,6 +29,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.AllArgsConstructor;
@@ -36,8 +39,20 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.InvalidJsonException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.springframework.stereotype.Component;
@Component
@@ -45,6 +60,8 @@ import org.springframework.stereotype.Component;
public final class LoanChargeApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
private final ChargeRepositoryWrapper chargeRepository;
private final LoanChargeRepository loanChargeRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
if (!dataValidationErrors.isEmpty()) {
@@ -274,4 +291,162 @@ public final class LoanChargeApiJsonValidator {
}
}
}
public void validateLoanCharges(JsonElement element, LoanProduct loanProduct, DataValidatorBuilder baseDataValidator) {
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.chargesParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(topLevelJsonElement);
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.chargesParameterName).isJsonArray()) {
final Type arrayObjectParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(
Arrays.asList(LoanApiConstants.idParameterName, LoanApiConstants.chargeIdParameterName,
LoanApiConstants.amountParameterName, LoanApiConstants.chargeTimeTypeParameterName,
LoanApiConstants.chargeCalculationTypeParameterName, LoanApiConstants.dueDateParamName,
LoanApiConstants.chargePaymentModeParameterName, LoanApiConstants.externalIdParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.chargesParameterName).getAsJsonArray();
for (int i = 1; i <= array.size(); i++) {
final JsonObject loanChargeElement = array.get(i - 1).getAsJsonObject();
final String arrayObjectJson = this.fromApiJsonHelper.toJson(loanChargeElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(arrayObjectParameterTypeOfMap, arrayObjectJson,
supportedParameters);
String chargeCurrencyCode;
Charge chargeDefinition;
ChargeTimeType chargeTime;
ChargeCalculationType chargeCalculation;
ChargePaymentMode chargePaymentModeEnum;
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
final Integer chargeCalculationType = this.fromApiJsonHelper.extractIntegerNamed("chargeCalculationType",
loanChargeElement, locale);
final Integer chargePaymentMode = this.fromApiJsonHelper.extractIntegerNamed("chargePaymentMode", loanChargeElement,
locale);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.chargeIdParameterName,
loanChargeElement);
final Long loanChargeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.idParameterName, element);
if (loanChargeId == null) {
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.chargeIdParameterName, i).value(chargeId).notNull()
.integerGreaterThanZero();
} else if (chargeId == null) {
baseDataValidator.reset().parameter(LoanApiConstants.idParameterName)
.parameterAtIndexArray(LoanApiConstants.idParameterName, i).value(loanChargeId).notNull()
.integerGreaterThanZero();
}
if (loanChargeId == null && chargeId == null) {
return;
}
if (chargeId != null) {
chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
chargeCurrencyCode = chargeDefinition.getCurrencyCode();
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
} else {
chargeTime = ChargeTimeType.fromInt(chargeDefinition.getChargeTimeType());
}
if (chargeCalculationType != null) {
chargeCalculation = ChargeCalculationType.fromInt(chargeCalculationType);
} else {
chargeCalculation = ChargeCalculationType.fromInt(chargeDefinition.getChargeCalculation());
}
if (chargePaymentMode != null) {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargePaymentMode);
} else {
chargePaymentModeEnum = ChargePaymentMode.fromInt(chargeDefinition.getChargePaymentMode());
}
} else {
LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
chargeDefinition = loanCharge.getCharge();
chargeCurrencyCode = chargeDefinition.getCurrencyCode();
chargeTime = loanCharge.getChargeTimeType();
chargeCalculation = loanCharge.getChargeCalculation();
chargePaymentModeEnum = loanCharge.getChargePaymentMode();
}
if (!loanProduct.hasCurrencyCodeOf(chargeCurrencyCode)) {
final String errorMessage = "Charge and Loan must have the same currency.";
// TODO: GeneralPlatformDomainRuleException vs PlatformApiDataValidationException
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName());
}
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.amountParameterName,
loanChargeElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.chargesParameterName)
.parameterAtIndexArray(LoanApiConstants.amountParameterName, i).value(amount).notNull().positiveAmount();
if (chargeTime.isSpecifiedDueDate()) {
LocalDate dueDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.dueDateParamName,
loanChargeElement, dateFormat, locale);
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
if (DateUtils.isBefore(dueDate, expectedDisbursementDate)) {
final String defaultUserMessage = "This charge with specified due date cannot be added as the it is not in schedule range.";
throw new LoanChargeCannotBeAddedException("loanCharge", "specified.due.date.outside.range", defaultUserMessage,
expectedDisbursementDate, chargeDefinition.getName());
}
}
if (chargePaymentMode != null) {
if (chargePaymentModeEnum.isPaymentModeAccountTransfer()) {
final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
if (savingsAccountId == null) {
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
validateInterestBearingLoanProductRestriction(chargeCalculation, chargeTime, loanProduct, baseDataValidator);
}
}
}
}
private void validateInterestBearingLoanProductRestriction(ChargeCalculationType chargeCalculationType, ChargeTimeType chargeTime,
LoanProduct loanProduct, DataValidatorBuilder baseDataValidator) {
if (loanProduct.isInterestRecalculationEnabled()) {
String errorcode = null;
switch (chargeCalculationType) {
case PERCENT_OF_AMOUNT:
if (chargeTime.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
}
break;
case PERCENT_OF_AMOUNT_AND_INTEREST:
if (chargeTime.isInstalmentFee()) {
errorcode = "installment." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_PRINCIPAL_CALCULATION_TYPE;
} else if (chargeTime.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
case PERCENT_OF_INTEREST:
if (chargeTime.isSpecifiedDueDate()) {
errorcode = "specific." + LoanApiConstants.LOAN_CHARGE_CAN_NOT_BE_ADDED_WITH_INTEREST_CALCULATION_TYPE;
}
break;
default:
break;
}
if (errorcode != null) {
baseDataValidator.reset().parameter("charges").failWithCode(errorcode);
}
}
}
}
@@ -59,7 +59,7 @@ import org.springframework.stereotype.Component;
public final class LoanEventApiJsonValidator {
private final FromJsonHelper fromApiJsonHelper;
private final LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer;
private final LoanApplicationValidator fromApiJsonDeserializer;
private final LoanRepository loanRepository;
private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
@@ -79,12 +79,12 @@ public final class LoanEventApiJsonValidator {
if (isAccountTransfer) {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.disbursementNetDisbursalAmountParameterName));
} else {
disbursementParameters = new HashSet<>(Arrays.asList("actualDisbursementDate", "externalId", "note", "locale", "dateFormat",
"paymentTypeId", "accountNumber", "checkNumber", "routingCode", "receiptNumber", "bankNumber", "adjustRepaymentDate",
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.principalDisbursedParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.postDatedChecks, LoanApiConstants.disbursementNetDisbursalAmountParameterName));
}
@@ -111,9 +111,9 @@ public final class LoanEventApiJsonValidator {
baseDataValidator.reset().parameter(LoanApiConstants.disbursementNetDisbursalAmountParameterName).value(netDisbursalAmount)
.ignoreIfNull().positiveAmount();
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName,
final BigDecimal emiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.emiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).ignoreIfNull().positiveAmount()
.notGreaterThanMax(principal);
validatePaymentDetails(baseDataValidator, element);
@@ -40,7 +40,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public final class CalculateLoanScheduleQueryFromApiJsonHelper {
public final class LoanScheduleValidator {
/**
* The parameters supported for this command.
@@ -64,7 +64,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
LoanApiConstants.submittedOnNoteParameterName, LoanApiConstants.localeParameterName, LoanApiConstants.dateFormatParameterName,
LoanApiConstants.chargesParameterName, LoanApiConstants.collateralParameterName,
LoanApiConstants.syncDisbursementWithMeetingParameterName, LoanApiConstants.linkAccountIdParameterName,
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.emiAmountParameterName,
LoanApiConstants.disbursementDataParameterName, LoanApiConstants.fixedEmiAmountParameterName,
LoanApiConstants.maxOutstandingBalanceParameterName, LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME,
LoanApiConstants.createStandingInstructionAtDisbursementParameterName, LoanApiConstants.isFloatingInterestRateParameterName,
LoanApiConstants.interestRateDifferentialParameterName, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
@@ -76,7 +76,7 @@ public final class CalculateLoanScheduleQueryFromApiJsonHelper {
private final FromJsonHelper fromApiJsonHelper;
@Autowired
public CalculateLoanScheduleQueryFromApiJsonHelper(final FromJsonHelper fromApiJsonHelper) {
public LoanScheduleValidator(final FromJsonHelper fromApiJsonHelper) {
this.fromApiJsonHelper = fromApiJsonHelper;
}
@@ -31,6 +31,6 @@ public interface GLIMAccountInfoWritePlatformService {
void incrementChildAccountCount(GroupLoanIndividualMonitoringAccount glimAccount);
void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId);
GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId);
}
@@ -21,30 +21,23 @@ package org.apache.fineract.portfolio.loanaccount.service;
import java.math.BigDecimal;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository;
@RequiredArgsConstructor
public class GLIMAccountInfoWritePlatformServiceImpl implements GLIMAccountInfoWritePlatformService {
private final PlatformSecurityContext context;
private final GLIMAccountInfoRepository glimAccountRepository;
private final LoanRepository loanRepository;
@Override
public void addGLIMAccountInfo(String accountNumber, Group group, BigDecimal principalAmount, Long childAccountsCount,
Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
public GroupLoanIndividualMonitoringAccount createGLIMAccount(String accountNumber, Group group, BigDecimal principalAmount,
Long childAccountsCount, Boolean isAcceptingChild, Integer loanStatus, BigDecimal applicationId) {
GroupLoanIndividualMonitoringAccount glimAccountInfo = GroupLoanIndividualMonitoringAccount.getInstance(accountNumber, group,
principalAmount, childAccountsCount, isAcceptingChild, loanStatus, applicationId);
this.glimAccountRepository.save(glimAccountInfo);
return this.glimAccountRepository.save(glimAccountInfo);
}
@Override
@@ -18,29 +18,35 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat;
import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
import org.apache.fineract.infrastructure.accountnumberformat.domain.EntityAccountType;
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.configuration.service.TemporaryConfigurationServiceContainer;
import org.apache.fineract.infrastructure.core.api.JsonCommand;
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
import org.apache.fineract.infrastructure.core.api.JsonQuery;
import org.apache.fineract.infrastructure.core.domain.ExternalId;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
import org.apache.fineract.organisation.monetary.exception.InvalidCurrencyException;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.staff.domain.Staff;
import org.apache.fineract.organisation.staff.domain.StaffRepository;
import org.apache.fineract.organisation.staff.exception.StaffNotFoundException;
@@ -48,21 +54,20 @@ import org.apache.fineract.organisation.staff.exception.StaffRoleException;
import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
import org.apache.fineract.organisation.workingdays.domain.WorkingDaysRepositoryWrapper;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.accountdetails.service.AccountEnumerations;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.client.domain.AccountNumberGenerator;
import org.apache.fineract.portfolio.client.domain.Client;
import org.apache.fineract.portfolio.client.domain.ClientRepositoryWrapper;
import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
import org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain;
import org.apache.fineract.portfolio.collateralmanagement.service.LoanCollateralAssembler;
import org.apache.fineract.portfolio.fund.domain.Fund;
import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.exception.FundNotFoundException;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.group.exception.ClientNotInGroupException;
import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
import org.apache.fineract.portfolio.group.exception.GroupNotFoundException;
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
@@ -72,21 +77,24 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachin
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTopupDetails;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.exception.ExceedingTrancheCountException;
import org.apache.fineract.portfolio.loanaccount.exception.InvalidAmountOfCollaterals;
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataNotAllowedException;
import org.apache.fineract.portfolio.loanaccount.exception.MultiDisbursementDataRequiredException;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanproduct.LoanProductConstants;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.exception.LinkedAccountRequiredException;
import org.apache.fineract.portfolio.loanproduct.exception.LoanProductNotFoundException;
import org.apache.fineract.portfolio.rate.domain.Rate;
import org.apache.fineract.portfolio.rate.service.RateAssembler;
@@ -98,7 +106,7 @@ public class LoanAssembler {
private final LoanRepositoryWrapper loanRepository;
private final LoanProductRepository loanProductRepository;
private final ClientRepositoryWrapper clientRepository;
private final GroupRepository groupRepository;
private final GroupRepositoryWrapper groupRepository;
private final FundRepository fundRepository;
private final StaffRepository staffRepository;
private final CodeValueRepositoryWrapper codeValueRepository;
@@ -110,10 +118,18 @@ public class LoanAssembler {
private final HolidayRepository holidayRepository;
private final ConfigurationDomainService configurationDomainService;
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanUtilService loanUtilService;
private final RateAssembler rateAssembler;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final ExternalIdFactory externalIdFactory;
private final AccountNumberFormatRepositoryWrapper accountNumberFormatRepository;
private final GLIMAccountInfoRepository glimRepository;
private final AccountNumberGenerator accountNumberGenerator;
private final GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService;
private final LoanCollateralAssembler loanCollateralAssembler;
private final LoanScheduleCalculationPlatformService calculationPlatformService;
private final LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler;
private final LoanChargeMapper loanChargeMapper;
private final LoanCollateralManagementMapper loanCollateralManagementMapper;
public Loan assembleFrom(final Long accountId) {
final Loan loanAccount = this.loanRepository.findOneWithNotFoundDetection(accountId, true);
@@ -133,20 +149,10 @@ public class LoanAssembler {
final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element);
return assembleApplication(element, clientId, groupId);
}
private Loan assembleApplication(final JsonElement element, final Long clientId, final Long groupId) {
final String accountNo = this.fromApiJsonHelper.extractStringNamed("accountNo", element);
final Long productId = this.fromApiJsonHelper.extractLongNamed("productId", element);
final Long fundId = this.fromApiJsonHelper.extractLongNamed("fundId", element);
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed("loanOfficerId", element);
final String transactionProcessingStrategyCode = this.fromApiJsonHelper.extractStringNamed("transactionProcessingStrategyCode",
element);
final String transactionProcessingStrategyName = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode).getName();
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed("loanPurposeId", element);
final Boolean syncDisbursementWithMeeting = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element);
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper
@@ -154,8 +160,13 @@ public class LoanAssembler {
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
final BigDecimal amount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.disbursementPrincipalParameterName, element);
final Boolean allowOverridingTransactionProcessingStrategy = loanProduct.getLoanConfigurableAttributes()
.getTransactionProcessingStrategyBoolean();
final String transactionProcessingStrategyCode = allowOverridingTransactionProcessingStrategy
? this.fromApiJsonHelper.extractStringNamed("transactionProcessingStrategyCode", element)
: loanProduct.getTransactionProcessingStrategyCode();
final LoanRepaymentScheduleTransactionProcessor transactionProcessingStrategy = this.loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
final Fund fund = findFundByIdIfProvided(fundId);
final Staff loanOfficer = findLoanOfficerByIdIfProvided(loanOfficerId);
CodeValue loanPurpose = null;
@@ -165,76 +176,26 @@ public class LoanAssembler {
List<LoanDisbursementDetails> disbursementDetails = new ArrayList<>();
BigDecimal fixedEmiAmount = null;
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element);
fixedEmiAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
}
BigDecimal maxOutstandingLoanBalance = null;
if (loanProduct.isMultiDisburseLoan()) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
maxOutstandingLoanBalance = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
element, locale);
disbursementDetails = this.loanUtilService.fetchDisbursementData(element.getAsJsonObject());
if (loanProduct.isDisallowExpectedDisbursements()) {
if (!disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDetails.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
}
if (disbursementDetails.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
disbursementDetails = this.loanDisbursementDetailsAssembler.fetchDisbursementData(element.getAsJsonObject());
}
final String loanTypeParameterName = "loanType";
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element);
final EnumOptionData loanType = AccountEnumerations.loanType(loanTypeStr);
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
Set<LoanCollateralManagement> collateral = new HashSet<>();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanAccountType = AccountType.fromName(loanTypeStr);
if (loanAccountType.isIndividualAccount()) {
collateral = this.collateralAssembler.fromParsedJson(element);
if (collateral.size() > 0) {
BigDecimal totalValue = BigDecimal.ZERO;
for (LoanCollateralManagement collateralManagement : collateral) {
final CollateralManagementDomain collateralManagementDomain = collateralManagement.getClientCollateralManagement()
.getCollaterals();
BigDecimal totalCollateral = collateralManagement.getQuantity().multiply(collateralManagementDomain.getBasePrice())
.multiply(collateralManagementDomain.getPctToBase()).divide(BigDecimal.valueOf(100));
totalValue = totalValue.add(totalCollateral);
}
if (amount.compareTo(totalValue) > 0) {
throw new InvalidAmountOfCollaterals(totalValue);
}
}
}
final AccountType loanAccountType = AccountType.fromName(loanTypeStr);
if (loanAccountType.isIndividualAccount()) {
collateral = this.collateralAssembler.fromParsedJson(element);
}
final Set<LoanCharge> loanCharges = this.loanChargeAssembler.fromParsedJson(element, disbursementDetails);
for (final LoanCharge loanCharge : loanCharges) {
if (!loanProduct.hasCurrencyCodeOf(loanCharge.currencyCode())) {
final String errorMessage = "Charge and Loan must have the same currency.";
throw new InvalidCurrencyException("loanCharge", "attach.to.loan", errorMessage);
}
if (loanCharge.getChargePaymentMode().isPaymentModeAccountTransfer()) {
final Long savingsAccountId = this.fromApiJsonHelper.extractLongNamed("linkAccountId", element);
if (savingsAccountId == null) {
final String errorMessage = "one of the charges requires linked savings account for payment";
throw new LinkedAccountRequiredException("loanCharge", errorMessage);
}
}
}
BigDecimal fixedPrincipalPercentagePerInstallment = fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
@@ -245,111 +206,166 @@ public class LoanAssembler {
// Here we add Rates to LoanApplication
final List<Rate> rates = this.rateAssembler.fromParsedJson(element);
final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element);
final LoanProductRelatedDetail loanProductRelatedDetail = this.loanScheduleAssembler.assembleLoanProductRelatedDetail(element,
loanProduct);
final BigDecimal interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
final Boolean isFloatingInterestRate = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element);
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
final LoanScheduleType loanScheduleType = loanProduct.getLoanProductRelatedDetail().getLoanScheduleType();
if (loanScheduleType.equals(LoanScheduleType.PROGRESSIVE)) {
if (!transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation",
"Loan repayment strategy can not be different than Advanced Payment Allocation");
}
// CUMULATIVE: Repayment strategy CANNOT be "advanced payment allocation"
} else if (loanScheduleType.equals(LoanScheduleType.CUMULATIVE)) {
if (transactionProcessingStrategyCode.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.equal.to.advanced.payment.allocation",
"Loan repayment strategy can not be equal to Advanced Payment Allocation");
}
}
if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
if (client.isNotActive()) {
throw new ClientNotActiveException(clientId);
}
}
if (groupId != null) {
group = this.groupRepository.findById(groupId).orElseThrow(() -> new GroupNotFoundException(groupId));
if (group.isNotActive()) {
throw new GroupNotActiveException(groupId);
}
}
if (client != null && group != null) {
if (!group.hasClientAsMember(client)) {
throw new ClientNotInGroupException(clientId, groupId);
}
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanType.getId().intValue(), loanProduct,
fund, loanOfficer, loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanType.getId().intValue(), loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, null,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanType.getId().intValue(), loanProduct, fund,
loanOfficer, loanPurpose, transactionProcessingStrategyCode, loanProductRelatedDetail, loanCharges, collateral,
fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement,
isFloatingInterestRate, interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment);
} else {
loanApplication = null;
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", element);
ExternalId externalId = externalIdFactory.create(externalIdStr);
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed("submittedOnDate", element);
if (loanApplication == null) {
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
loanApplication.updateTransactionProcessingStrategy(transactionProcessingStrategyCode, transactionProcessingStrategyName);
copyAdvancedPaymentRulesIfApplicable(transactionProcessingStrategyCode, loanProduct, loanApplication);
loanApplication.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
if (loanProduct.isMultiDisburseLoan()) {
for (final LoanDisbursementDetails loanDisbursementDetails : loanApplication.getDisbursementDetails()) {
loanDisbursementDetails.updateLoan(loanApplication);
}
}
final Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element);
if (isEnableInstallmentLevelDelinquency != null) {
loanApplication.updateEnableInstallmentLevelDelinquency(isEnableInstallmentLevelDelinquency);
} else {
loanApplication.updateEnableInstallmentLevelDelinquency(loanProduct.isEnableInstallmentLevelDelinquency());
if (isEnableInstallmentLevelDelinquency == null) {
isEnableInstallmentLevelDelinquency = loanProduct.isEnableInstallmentLevelDelinquency();
}
final LoanApplicationTerms loanApplicationTerms = this.loanScheduleAssembler.assembleLoanTerms(element);
final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
Long officeId = client != null ? client.getOffice().getId() : group.getOffice().getId();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId,
loanApplicationTerms.getExpectedDisbursementDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final LoanScheduleModel loanScheduleModel = this.loanScheduleAssembler.assembleLoanScheduleFrom(loanApplicationTerms,
isHolidayEnabled, holidays, workingDays, element, disbursementDetails);
loanApplication.loanApplicationSubmittal(loanScheduleModel, loanApplicationTerms, defaultLoanLifecycleStateMachine, submittedOnDate,
externalId, allowTransactionsOnHoliday, holidays, workingDays, allowTransactionsOnNonWorkingDay);
if (client != null && group != null) {
loanApplication = Loan.newIndividualLoanApplicationFromGroup(accountNo, client, group, loanAccountType, loanProduct, fund,
loanOfficer, loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges,
syncDisbursementWithMeeting, fixedEmiAmount, disbursementDetails, maxOutstandingLoanBalance,
createStandingInstructionAtDisbursement, isFloatingInterestRate, interestRateDifferential, rates,
fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms, loanScheduleModel,
isEnableInstallmentLevelDelinquency, submittedOnDate);
} else if (group != null) {
loanApplication = Loan.newGroupLoanApplication(accountNo, group, loanAccountType, loanProduct, fund, loanOfficer, loanPurpose,
transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, syncDisbursementWithMeeting, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
} else if (client != null) {
loanApplication = Loan.newIndividualLoanApplication(accountNo, client, loanAccountType, loanProduct, fund, loanOfficer,
loanPurpose, transactionProcessingStrategy, loanProductRelatedDetail, loanCharges, collateral, fixedEmiAmount,
disbursementDetails, maxOutstandingLoanBalance, createStandingInstructionAtDisbursement, isFloatingInterestRate,
interestRateDifferential, rates, fixedPrincipalPercentagePerInstallment, externalId, loanApplicationTerms,
loanScheduleModel, isEnableInstallmentLevelDelinquency, submittedOnDate);
} else {
throw new IllegalStateException("No loan application exists for either a client or group (or both).");
}
copyAdvancedPaymentRulesIfApplicable(transactionProcessingStrategyCode, loanProduct, loanApplication);
loanApplication.setHelpers(defaultLoanLifecycleStateMachine, this.loanSummaryWrapper,
this.loanRepaymentScheduleTransactionProcessorFactory);
// TODO: review
loanApplication.recalculateAllCharges();
topUpLoanConfiguration(element, loanApplication);
return loanApplication;
}
// TODO: Review... it might be better somewhere else and rethink due to the account number generation logic is
// intertwined with GLIM logic
public void accountNumberGeneration(JsonCommand command, Loan loan) {
if (loan.isAccountNumberRequiresAutoGeneration()) {
JsonElement element = command.parsedJson();
final AccountNumberFormat accountNumberFormat = this.accountNumberFormatRepository.findByAccountType(EntityAccountType.LOAN);
// TODO: It is really weird to set GLIM info only if account number was not provided
// if application is of GLIM type
if (loan.getLoanType().isGLIMAccount()) {
Group group = loan.getGroup();
String accountNumber = "";
BigDecimal applicationId = BigDecimal.ZERO;
Boolean isLastChildApplication = false;
// GLIM specific parameters
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(element.getAsJsonObject());
BigDecimal applicationIdFromParam = this.fromApiJsonHelper.extractBigDecimalNamed("applicationId", element, locale);
BigDecimal totalLoan = this.fromApiJsonHelper.extractBigDecimalNamed("totalLoan", element, locale);
if (applicationIdFromParam != null) {
applicationId = applicationIdFromParam;
}
Boolean isLastChildApplicationFromParam = this.fromApiJsonHelper.extractBooleanNamed("lastApplication", element);
if (isLastChildApplicationFromParam != null) {
isLastChildApplication = isLastChildApplicationFromParam;
}
if (this.fromApiJsonHelper.extractBooleanNamed("isParentAccount", element) != null) {
// empty table check
// TODO: This count here is weird... and seems parent-empty and parent not empty looks the same
if (glimRepository.count() != 0) {
// **************Parent-Not an empty
// table********************
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
} else {
// ************** Parent-empty
// table********************
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
}
} else {
// TODO: This count here is weird...
if (glimRepository.count() != 0) {
// Child-Not an empty table
GroupLoanIndividualMonitoringAccount glimAccount = glimRepository.findOneByIsAcceptingChildAndApplicationId(true,
applicationId);
accountNumber = glimAccount.getAccountNumber() + (glimAccount.getChildAccountsCount() + 1);
loan.updateAccountNo(accountNumber);
this.glimAccountInfoWritePlatformService.incrementChildAccountCount(glimAccount);
loan.setGlim(glimAccount);
} else {
// **************Child-empty
// table********************
// if the glim info is empty set the current account
// as parent
createAndSetGLIMAccount(totalLoan, loan, accountNumberFormat, group, applicationId);
}
// reset in cases of last child application of glim
if (isLastChildApplication) {
this.glimAccountInfoWritePlatformService
.resetIsAcceptingChild(glimRepository.findOneByIsAcceptingChildAndApplicationId(true, applicationId));
}
}
} else { // for applications other than GLIM
loan.updateAccountNo(this.accountNumberGenerator.generate(loan, accountNumberFormat));
}
}
}
private void createAndSetGLIMAccount(BigDecimal totalLoan, Loan loan, AccountNumberFormat accountNumberFormat, Group group,
BigDecimal applicationId) {
String accountNumber;
accountNumber = this.accountNumberGenerator.generate(loan, accountNumberFormat);
loan.updateAccountNo(accountNumber + "1");
GroupLoanIndividualMonitoringAccount glimAccount = glimAccountInfoWritePlatformService.createGLIMAccount(accountNumber, group,
totalLoan, 1L, true, LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), applicationId);
loan.setGlim(glimAccount);
}
private void topUpLoanConfiguration(JsonElement element, Loan loan) {
if (loan.getLoanProduct().isCanUseForTopup() && loan.getClientId() != null) {
final Boolean isTopUp = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element);
if (null == isTopUp) {
loan.setIsTopup(false);
} else {
loan.setIsTopup(isTopUp);
}
if (loan.isTopup()) {
final Long loanIdToClose = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
loan.setTopupLoanDetails(new LoanTopupDetails(loan, loanIdToClose));
}
}
}
public CodeValue findCodeValueByIdIfProvided(final Long codeValueId) {
CodeValue codeValue = null;
if (codeValueId != null) {
@@ -377,18 +393,6 @@ public class LoanAssembler {
return staff;
}
public void validateExpectedDisbursementForHolidayAndNonWorkingDay(final Loan loanApplication) {
final boolean allowTransactionsOnHoliday = this.configurationDomainService.allowTransactionsOnHolidayEnabled();
final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(loanApplication.getOfficeId(),
loanApplication.getExpectedDisbursedOnLocalDate(), HolidayStatusType.ACTIVE.getValue());
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
loanApplication.validateExpectedDisbursementForHolidayAndNonWorkingDay(workingDays, allowTransactionsOnHoliday, holidays,
allowTransactionsOnNonWorkingDay);
}
private void copyAdvancedPaymentRulesIfApplicable(String transactionProcessingStrategyCode, LoanProduct loanProduct,
Loan loanApplication) {
if (transactionProcessingStrategyCode.equals(AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
@@ -398,11 +402,435 @@ public class LoanAssembler {
.toList();
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules);
if (loanProduct.getCreditAllocationRules() != null && loanProduct.getCreditAllocationRules().size() > 0) {
if (loanProduct.getCreditAllocationRules() != null && !loanProduct.getCreditAllocationRules().isEmpty()) {
List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream()
.map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList();
loanApplication.setCreditAllocationRules(loanCreditAllocationRules);
}
}
}
public Map<String, Object> updateFrom(JsonCommand command, Loan loan) {
final Map<String, Object> changes = new HashMap<>();
LoanProduct loanProduct;
final String productIdParamName = "productId";
final Long productId = command.longValueOfParameterNamed(productIdParamName);
if (productId == null || productId.equals(loan.getLoanProduct().getId())) {
loanProduct = loan.getLoanProduct();
} else {
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
}
final Set<LoanCharge> existingCharges = loan.getActiveCharges();
Map<Long, LoanChargeData> chargesMap = new HashMap<>();
for (LoanCharge charge : existingCharges) {
LoanChargeData chargeData = new LoanChargeData(charge.getId(), charge.getDueLocalDate(), charge.amountOrPercentage());
chargesMap.put(charge.getId(), chargeData);
}
List<LoanDisbursementDetails> disbursementDetails = this.loanDisbursementDetailsAssembler
.fetchDisbursementData(command.parsedJson().getAsJsonObject());
/**
* Stores all charges which are passed in during modify loan application
**/
final Set<LoanCharge> possiblyModifiedLoanCharges = this.loanChargeAssembler.fromParsedJson(command.parsedJson(),
disbursementDetails);
/** Boolean determines if any charge has been modified **/
boolean isChargeModified = false;
Set<Charge> newTrancheCharges = this.loanChargeAssembler.getNewLoanTrancheCharges(command.parsedJson());
for (Charge charge : newTrancheCharges) {
loan.addTrancheLoanCharge(charge);
}
/**
* If there are any charges already present, which are now not passed in as a part of the request, deem the
* charges as modified
**/
if (!possiblyModifiedLoanCharges.isEmpty()) {
if (!possiblyModifiedLoanCharges.containsAll(existingCharges)) {
isChargeModified = true;
}
}
/**
* If any new charges are added or values of existing charges are modified
**/
for (LoanCharge loanCharge : possiblyModifiedLoanCharges) {
if (loanCharge.getId() == null) {
isChargeModified = true;
} else {
LoanChargeData chargeData = chargesMap.get(loanCharge.getId());
if (loanCharge.amountOrPercentage().compareTo(chargeData.getAmountOrPercentage()) != 0
|| (loanCharge.isSpecifiedDueDate() && !loanCharge.getDueLocalDate().equals(chargeData.getDueDate()))) {
isChargeModified = true;
}
}
}
Set<LoanCollateralManagement> possiblyModifedLoanCollateralItems = null;
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
possiblyModifedLoanCollateralItems = this.loanCollateralAssembler.fromParsedJson(command.parsedJson());
}
}
this.loanScheduleAssembler.updateLoanApplicationAttributes(command, loan, changes);
if (!changes.isEmpty()) {
final boolean recalculateLoanSchedule = !(changes.size() == 1
&& changes.containsKey(LoanApiConstants.inArrearsToleranceParameterName));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, recalculateLoanSchedule);
isChargeModified = true;
}
final String dateFormatAsInput = command.dateFormat();
final String localeAsInput = command.locale();
if (command.isChangeInStringParameterNamed(LoanApiConstants.accountNoParameterName, loan.getAccountNumber())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.accountNoParameterName);
changes.put(LoanApiConstants.accountNoParameterName, newValue);
loan.setAccountNumber(StringUtils.defaultIfEmpty(newValue, null));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName,
loan.shouldCreateStandingInstructionAtDisbursement())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName);
changes.put(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, valueAsInput);
loan.setCreateStandingInstructionAtDisbursement(valueAsInput);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.externalIdParameterName, loan.getExternalId().getValue())) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.externalIdParameterName);
ExternalId externalId = ExternalIdFactory.produce(newValue);
if (externalId.isEmpty() && TemporaryConfigurationServiceContainer.isExternalIdAutoGenerationEnabled()) {
externalId = ExternalId.generate();
}
changes.put(LoanApiConstants.externalIdParameterName, externalId);
loan.setExternalId(externalId);
}
// add clientId, groupId and loanType changes to actual changes
final Long clientId = loan.getClient() == null ? null : loan.getClient().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.clientIdParameterName, clientId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.clientIdParameterName);
changes.put(LoanApiConstants.clientIdParameterName, newValue);
final Client client = this.clientRepository.findOneWithNotFoundDetection(newValue);
loan.updateClient(client);
}
// FIXME: AA - We may require separate api command to move loan from one
// group to another
final Long groupId = loan.getGroup() == null ? null : loan.getGroup().getId();
if (command.isChangeInLongParameterNamed(LoanApiConstants.groupIdParameterName, groupId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.groupIdParameterName);
changes.put(LoanApiConstants.groupIdParameterName, newValue);
final Group group = this.groupRepository.findOneWithNotFoundDetection(newValue);
loan.updateGroup(group);
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.productIdParameterName, loan.getLoanProduct().getId())) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.productIdParameterName);
changes.put(LoanApiConstants.productIdParameterName, newValue);
loan.updateLoanProduct(loanProduct);
final MonetaryCurrency currency = new MonetaryCurrency(loanProduct.getCurrency().getCode(),
loanProduct.getCurrency().getDigitsAfterDecimal(), loanProduct.getCurrency().getCurrencyInMultiplesOf());
loan.getLoanRepaymentScheduleDetail().setCurrency(currency);
if (!changes.containsKey(LoanApiConstants.interestRateFrequencyTypeParameterName)) {
loan.updateInterestRateFrequencyType();
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
loan.getLoanProductRelatedDetail().updateForFloatingInterestRates();
} else {
loan.setInterestRateDifferential(null);
loan.setIsFloatingInterestRate(null);
}
loan.updateIsInterestRecalculationEnabled();
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName,
loan.getIsFloatingInterestRate())) {
final Boolean newValue = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isFloatingInterestRateParameterName);
changes.put(LoanApiConstants.isFloatingInterestRateParameterName, newValue);
loan.setIsFloatingInterestRate(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.interestRateDifferentialParameterName,
loan.getInterestRateDifferential())) {
final BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.interestRateDifferentialParameterName);
changes.put(LoanApiConstants.interestRateDifferentialParameterName, newValue);
loan.setInterestRateDifferential(newValue);
}
Long existingFundId = null;
if (loan.getFund() != null) {
existingFundId = loan.getFund().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.fundIdParameterName, existingFundId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.fundIdParameterName);
changes.put(LoanApiConstants.fundIdParameterName, newValue);
final Fund fund = findFundByIdIfProvided(newValue);
loan.updateFund(fund);
}
Long existingLoanOfficerId = null;
if (loan.getLoanOfficer() != null) {
existingLoanOfficerId = loan.getLoanOfficer().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanOfficerIdParameterName, existingLoanOfficerId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanOfficerIdParameterName);
changes.put(LoanApiConstants.loanOfficerIdParameterName, newValue);
final Staff newOfficer = findLoanOfficerByIdIfProvided(newValue);
loan.updateLoanOfficerOnLoanApplication(newOfficer);
}
Long existingLoanPurposeId = null;
if (loan.getLoanPurpose() != null) {
existingLoanPurposeId = loan.getLoanPurpose().getId();
}
if (command.isChangeInLongParameterNamed(LoanApiConstants.loanPurposeIdParameterName, existingLoanPurposeId)) {
final Long newValue = command.longValueOfParameterNamed(LoanApiConstants.loanPurposeIdParameterName);
changes.put(LoanApiConstants.loanPurposeIdParameterName, newValue);
final CodeValue loanPurpose = findCodeValueByIdIfProvided(newValue);
loan.updateLoanPurpose(loanPurpose);
}
if (command.isChangeInStringParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName,
loan.getTransactionProcessingStrategyCode())
&& loanProduct.getLoanConfigurableAttributes().getTransactionProcessingStrategyBoolean()) {
final String newValue = command.stringValueOfParameterNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName);
final String transactionProcessingStrategyCode = command.stringValueOfParameterNamed("transactionProcessingStrategyCode");
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessorFactory
.determineProcessor(transactionProcessingStrategyCode);
changes.put(LoanApiConstants.transactionProcessingStrategyCodeParameterName, newValue);
loan.updateTransactionProcessingStrategy(transactionProcessingStrategyCode,
loanRepaymentScheduleTransactionProcessor.getName());
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.submittedOnDateParameterName, loan.getSubmittedOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName);
changes.put(LoanApiConstants.submittedOnDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
loan.setSubmittedOnDate(command.localDateValueOfParameterNamed(LoanApiConstants.submittedOnDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName,
loan.getExpectedDisbursedOnLocalDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName);
changes.put(LoanApiConstants.expectedDisbursementDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedDisbursementDate(
command.localDateValueOfParameterNamed(LoanApiConstants.expectedDisbursementDateParameterName));
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName,
loan.getExpectedFirstRepaymentOnDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName);
changes.put(LoanApiConstants.repaymentsStartingFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setExpectedFirstRepaymentOnDate(
command.localDateValueOfParameterNamed(LoanApiConstants.repaymentsStartingFromDateParameterName));
}
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName,
loan.isSyncDisbursementWithMeeting())) {
final Boolean valueAsInput = command
.booleanObjectValueOfParameterNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName);
changes.put(LoanApiConstants.syncDisbursementWithMeetingParameterName, valueAsInput);
loan.setSyncDisbursementWithMeeting(valueAsInput);
}
if (command.isChangeInLocalDateParameterNamed(LoanApiConstants.interestChargedFromDateParameterName,
loan.getInterestChargedFromDate())) {
final String valueAsInput = command.stringValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName);
changes.put(LoanApiConstants.interestChargedFromDateParameterName, valueAsInput);
changes.put(LoanApiConstants.dateFormatParameterName, dateFormatAsInput);
changes.put(LoanApiConstants.localeParameterName, localeAsInput);
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
loan.setInterestChargedFromDate(command.localDateValueOfParameterNamed(LoanApiConstants.interestChargedFromDateParameterName));
}
if (isChargeModified) {
changes.put(LoanApiConstants.chargesParameterName, loanChargeMapper.map(possiblyModifiedLoanCharges, loan.getCurrency()));
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
if (command.parameterExists(LoanApiConstants.collateralParameterName) && possiblyModifedLoanCollateralItems != null
&& possiblyModifedLoanCollateralItems.equals(loan.getLoanCollateralManagements())) {
changes.put(LoanApiConstants.collateralParameterName, loanCollateralManagementMapper.map(possiblyModifedLoanCollateralItems));
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyParameterName, loan.getTermFrequency())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyParameterName);
changes.put(LoanApiConstants.loanTermFrequencyParameterName, newValue);
loan.setTermFrequency(newValue);
}
if (command.isChangeInIntegerParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName,
loan.getTermPeriodFrequencyType())) {
final Integer newValue = command.integerValueOfParameterNamed(LoanApiConstants.loanTermFrequencyTypeParameterName);
changes.put(LoanApiConstants.loanTermFrequencyTypeParameterName, newValue);
loan.setTermPeriodFrequencyType(newValue);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getApprovedPrincipal())) {
loan.setApprovedPrincipal(command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName));
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.principalParameterName, loan.getProposedPrincipal())) {
BigDecimal newValue = command.bigDecimalValueOfParameterNamed(LoanApiConstants.principalParameterName);
changes.put(LoanApiConstants.principalParameterName, newValue);
loan.setProposedPrincipal(newValue);
}
if (loanProduct.isMultiDisburseLoan()) {
loan.updateDisbursementDetails(command, changes);
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName,
loan.getMaxOutstandingLoanBalance())) {
loan.setMaxOutstandingLoanBalance(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.maxOutstandingBalanceParameterName));
}
final JsonArray disbursementDataArray = command.arrayOfParameterNamed(LoanApiConstants.disbursementDataParameterName);
if (loanProduct.isDisallowExpectedDisbursements()) {
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details are not allowed";
throw new MultiDisbursementDataNotAllowedException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
} else {
if (disbursementDataArray == null || disbursementDataArray.isEmpty()) {
final String errorMessage = "For this loan product, disbursement details must be provided";
throw new MultiDisbursementDataRequiredException(LoanApiConstants.disbursementDataParameterName, errorMessage);
}
if (disbursementDataArray.size() > loanProduct.maxTrancheCount()) {
final String errorMessage = "Number of tranche shouldn't be greter than " + loanProduct.maxTrancheCount();
throw new ExceedingTrancheCountException(LoanApiConstants.disbursementDataParameterName, errorMessage,
loanProduct.maxTrancheCount(), disbursementDetails.size());
}
}
} else {
loan.clearDisbursementDetails();
}
if (loanProduct.isMultiDisburseLoan() || loanProduct.isCanDefineInstallmentAmount()) {
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount())) {
loan.setFixedEmiAmount(command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName));
changes.put(LoanApiConstants.fixedEmiAmountParameterName, loan.getFixedEmiAmount());
changes.put(Loan.RECALCULATE_LOAN_SCHEDULE, true);
}
} else {
loan.setFixedEmiAmount(null);
}
if (command.isChangeInBigDecimalParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName,
loan.getFixedPrincipalPercentagePerInstallment())) {
loan.setFixedPrincipalPercentagePerInstallment(
command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName));
changes.put(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, loan.getFixedPrincipalPercentagePerInstallment());
}
final LoanProductRelatedDetail productRelatedDetail = loan.repaymentScheduleDetail();
if (loan.loanProduct().getLoanConfigurableAttributes() != null) {
loanScheduleAssembler.updateProductRelatedDetails(productRelatedDetail, loan);
}
if (loan.getLoanProduct().isCanUseForTopup() && loan.getClientId() != null) {
final Boolean isTopup = command.booleanObjectValueOfParameterNamed(LoanApiConstants.isTopup);
if (command.isChangeInBooleanParameterNamed(LoanApiConstants.isTopup, loan.isTopup())) {
loan.setIsTopup(isTopup);
changes.put(LoanApiConstants.isTopup, isTopup);
}
if (loan.isTopup()) {
final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
LoanTopupDetails existingLoanTopupDetails = loan.getTopupLoanDetails();
if (existingLoanTopupDetails == null || !existingLoanTopupDetails.getLoanIdToClose().equals(loanIdToClose)
|| changes.containsKey("submittedOnDate") || changes.containsKey("expectedDisbursementDate")
|| changes.containsKey("principal") || changes.containsKey(LoanApiConstants.disbursementDataParameterName)) {
Long existingLoanIdToClose = null;
if (existingLoanTopupDetails != null) {
existingLoanIdToClose = existingLoanTopupDetails.getLoanIdToClose();
}
if (!loanIdToClose.equals(existingLoanIdToClose)) {
final LoanTopupDetails topupDetails = new LoanTopupDetails(loan, loanIdToClose);
loan.setTopupLoanDetails(topupDetails);
changes.put(LoanApiConstants.loanIdToClose, loanIdToClose);
}
}
} else {
loan.setTopupLoanDetails(null);
}
} else {
if (loan.isTopup()) {
loan.setIsTopup(false);
loan.setTopupLoanDetails(null);
changes.put(LoanApiConstants.isTopup, false);
}
}
/**
* TODO: Allow other loan types if needed.
*/
if (command.parameterExists("loanType")) {
final String loanTypeStr = command.stringValueOfParameterNamed("loanType");
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (!StringUtils.isBlank(loanTypeStr) && loanType.isIndividualAccount()) {
final String collateralParamName = "collateral";
if (changes.containsKey(collateralParamName)) {
loan.updateLoanCollateral(possiblyModifedLoanCollateralItems);
}
}
}
final String chargesParamName = "charges";
if (changes.containsKey(chargesParamName)) {
loan.updateLoanCharges(possiblyModifiedLoanCharges);
}
// update installment level delinquency
if (command.isChangeInBooleanParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY,
loan.isEnableInstallmentLevelDelinquency())) {
final Boolean enableInstallmentLevelDelinquency = command
.booleanObjectValueOfParameterNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY);
loan.updateEnableInstallmentLevelDelinquency(enableInstallmentLevelDelinquency);
}
if (changes.containsKey("recalculateLoanSchedule")) {
changes.remove("recalculateLoanSchedule");
final JsonElement parsedQuery = this.fromApiJsonHelper.parse(command.json());
final JsonQuery query = JsonQuery.from(command.json(), parsedQuery, this.fromApiJsonHelper);
final LoanScheduleModel loanSchedule = this.calculationPlatformService.calculateLoanSchedule(query, false);
loan.updateLoanSchedule(loanSchedule);
loan.recalculateAllCharges();
}
// Changes to modify loan rates.
if (command.hasParameter(LoanProductConstants.RATES_PARAM_NAME)) {
loan.updateLoanRates(rateAssembler.fromParsedJson(command.parsedJson()));
}
return changes;
}
}
@@ -40,7 +40,7 @@ import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
import org.apache.fineract.portfolio.charge.exception.LoanChargeCannotBeAddedException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeNotFoundException;
import org.apache.fineract.portfolio.charge.exception.LoanChargeWithoutMandatoryFieldException;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -65,13 +65,13 @@ public class LoanChargeAssembler {
JsonArray jsonDisbursement = this.fromApiJsonHelper.extractJsonArrayNamed("disbursementData", element);
List<Long> disbursementChargeIds = new ArrayList<>();
if (jsonDisbursement != null && jsonDisbursement.size() > 0) {
if (jsonDisbursement != null && !jsonDisbursement.isEmpty()) {
for (int i = 0; i < jsonDisbursement.size(); i++) {
final JsonObject jsonObject = jsonDisbursement.get(i).getAsJsonObject();
if (jsonObject != null && jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName) != null) {
String chargeIds = jsonObject.getAsJsonPrimitive(LoanApiConstants.loanChargeIdParameterName).getAsString();
if (chargeIds != null) {
if (chargeIds.indexOf(",") != -1) {
if (chargeIds.contains(",")) {
Iterable<String> chargeId = Splitter.on(',').split(chargeIds);
for (String loanChargeId : chargeId) {
disbursementChargeIds.add(Long.parseLong(loanChargeId));
@@ -104,7 +104,7 @@ public class LoanChargeAssembler {
final JsonObject loanChargeElement = array.get(i).getAsJsonObject();
final Long id = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long loanChargeId = this.fromApiJsonHelper.extractLongNamed("id", loanChargeElement);
final Long chargeId = this.fromApiJsonHelper.extractLongNamed("chargeId", loanChargeElement);
final BigDecimal amount = this.fromApiJsonHelper.extractBigDecimalNamed("amount", loanChargeElement, locale);
final Integer chargeTimeType = this.fromApiJsonHelper.extractIntegerNamed("chargeTimeType", loanChargeElement, locale);
@@ -116,16 +116,8 @@ public class LoanChargeAssembler {
locale);
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", loanChargeElement);
final ExternalId externalId = externalIdFactory.create(externalIdStr);
if (id == null) {
if (loanChargeId == null) {
final Charge chargeDefinition = this.chargeRepository.findOneWithNotFoundDetection(chargeId);
if (chargeDefinition.isOverdueInstallment()) {
final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName());
}
ChargeTimeType chargeTime = null;
if (chargeTimeType != null) {
chargeTime = ChargeTimeType.fromInt(chargeTimeType);
@@ -145,7 +137,7 @@ public class LoanChargeAssembler {
} else {
if (topLevelJsonElement.has("disbursementData") && topLevelJsonElement.get("disbursementData").isJsonArray()) {
final JsonArray disbursementArray = topLevelJsonElement.get("disbursementData").getAsJsonArray();
if (disbursementArray.size() > 0) {
if (!disbursementArray.isEmpty()) {
JsonObject disbursementDataElement = disbursementArray.get(0).getAsJsonObject();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, disbursementDataElement, dateFormat,
@@ -201,14 +193,14 @@ public class LoanChargeAssembler {
}
}
} else {
final Long loanChargeId = id;
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId).orElse(null);
if (loanCharge != null) {
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
final LoanCharge loanCharge = this.loanChargeRepository.findById(loanChargeId)
.orElseThrow(() -> new LoanChargeNotFoundException(loanChargeId));
if (!loanCharge.isTrancheDisbursementCharge() || disbursementChargeIds.contains(loanChargeId)) {
loanCharge.update(amount, dueDate, numberOfRepayments);
loanCharges.add(loanCharge);
}
}
}
}
@@ -70,7 +70,6 @@ import org.apache.fineract.portfolio.account.domain.AccountTransferType;
import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundException;
import org.apache.fineract.portfolio.account.service.AccountAssociationsReadPlatformService;
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
import org.apache.fineract.portfolio.accountdetails.domain.AccountType;
import org.apache.fineract.portfolio.charge.domain.Charge;
import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
@@ -710,7 +709,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
paymentDetail);
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
@@ -0,0 +1,59 @@
/**
* 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.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
@RequiredArgsConstructor
public class LoanDisbursementDetailsAssembler {
private final FromJsonHelper fromApiJsonHelper;
public List<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
List<LoanDisbursementDetails> disbursementDatas = new ArrayList<>();
JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && !disbursementDataArray.isEmpty()) {
disbursementDataArray.forEach(jsonElement -> {
LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, jsonElement, dateFormat, locale);
BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.disbursementPrincipalParameterName,
jsonElement, locale);
BigDecimal netDisbursalAmount = this.fromApiJsonHelper
.extractBigDecimalNamed(LoanApiConstants.disbursementNetDisbursalAmountParameterName, jsonElement, locale);
boolean isReversed = Boolean.TRUE.equals(
this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.disbursementReversedParameterName, jsonElement));
disbursementDatas
.add(new LoanDisbursementDetails(expectedDisbursementDate, null, principal, netDisbursalAmount, isReversed));
});
}
return disbursementDatas;
}
}
@@ -18,19 +18,13 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.core.exception.PlatformServiceUnavailableException;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.organisation.holiday.domain.Holiday;
import org.apache.fineract.organisation.holiday.domain.HolidayRepository;
import org.apache.fineract.organisation.holiday.domain.HolidayStatusType;
@@ -52,11 +46,9 @@ import org.apache.fineract.portfolio.floatingrates.data.FloatingRatePeriodData;
import org.apache.fineract.portfolio.floatingrates.exception.FloatingRateNotFoundException;
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.group.domain.Group;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
@@ -71,7 +63,6 @@ public class LoanUtilService {
private final WorkingDaysRepositoryWrapper workingDaysRepository;
private final LoanScheduleGeneratorFactory loanScheduleFactory;
private final FloatingRatesReadPlatformService floatingRatesReadPlatformService;
private final FromJsonHelper fromApiJsonHelper;
private final CalendarReadPlatformService calendarReadPlatformService;
public ScheduleGeneratorDTO buildScheduleGeneratorDTO(final Loan loan, final LocalDate recalculateFrom) {
@@ -288,51 +279,6 @@ public class LoanUtilService {
return calculatedRepaymentsStartingFromDate;
}
public List<LoanDisbursementDetails> fetchDisbursementData(final JsonObject command) {
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(command);
final String dateFormat = this.fromApiJsonHelper.extractDateFormatParameter(command);
List<LoanDisbursementDetails> disbursementDatas = new ArrayList<>();
if (command.has(LoanApiConstants.disbursementDataParameterName)) {
final JsonArray disbursementDataArray = command.getAsJsonArray(LoanApiConstants.disbursementDataParameterName);
if (disbursementDataArray != null && disbursementDataArray.size() > 0) {
int i = 0;
do {
final JsonObject jsonObject = disbursementDataArray.get(i).getAsJsonObject();
LocalDate expectedDisbursementDate = null;
LocalDate actualDisbursementDate = null;
BigDecimal principal = null;
BigDecimal netDisbursalAmount = null;
if (jsonObject.has(LoanApiConstants.expectedDisbursementDateParameterName)) {
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(
LoanApiConstants.expectedDisbursementDateParameterName, jsonObject, dateFormat, locale);
}
if (jsonObject.has(LoanApiConstants.disbursementPrincipalParameterName)
&& jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank(jsonObject.get(LoanApiConstants.disbursementPrincipalParameterName).getAsString())) {
principal = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementPrincipalParameterName).getAsBigDecimal();
}
if (jsonObject.has(LoanApiConstants.disbursementNetDisbursalAmountParameterName)
&& jsonObject.get(LoanApiConstants.disbursementNetDisbursalAmountParameterName).isJsonPrimitive()
&& StringUtils.isNotBlank(
jsonObject.get(LoanApiConstants.disbursementNetDisbursalAmountParameterName).getAsString())) {
netDisbursalAmount = jsonObject.getAsJsonPrimitive(LoanApiConstants.disbursementNetDisbursalAmountParameterName)
.getAsBigDecimal();
}
boolean isReversed = false;
if (jsonObject.has(LoanApiConstants.disbursementReversedParameterName)) {
isReversed = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.disbursementReversedParameterName,
jsonObject);
}
disbursementDatas.add(new LoanDisbursementDetails(expectedDisbursementDate, actualDisbursementDate, principal,
netDisbursalAmount, isReversed));
i++;
} while (i < disbursementDataArray.size());
}
}
return disbursementDatas;
}
public void validateRepaymentTransactionType(LoanTransactionType repaymentTransactionType) {
if (!repaymentTransactionType.isRepaymentType()) {
throw new PlatformServiceUnavailableException("error.msg.repaymentTransactionType.provided.not.a.repayment.type",
@@ -147,7 +147,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
@@ -180,7 +179,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanSchedul
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -225,7 +224,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final FromJsonHelper fromApiJsonHelper;
private final CalendarRepository calendarRepository;
private final LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService;
private final LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper;
private final LoanApplicationValidator loanApplicationCommandFromApiJsonHelper;
private final AccountAssociationsRepository accountAssociationRepository;
private final AccountTransferDetailRepository accountTransferDetailRepository;
private final BusinessEventNotifierService businessEventNotifierService;
@@ -240,7 +239,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
private final LoanRepository loanRepository;
private final RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler;
private final PostDatedChecksRepository postDatedChecksRepository;
private final LoanDisbursementDetailsRepository loanDisbursementDetailsRepository;
private final LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository;
private final LoanLifecycleStateMachine defaultLoanLifecycleStateMachine;
private final LoanAccountLockService loanAccountLockService;
@@ -327,8 +325,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
// Get relevant loan collateral modules
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty())
&& AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if ((loanCollateralManagements != null && !loanCollateralManagements.isEmpty()) && loan.getLoanType().isIndividualAccount()) {
BigDecimal totalCollateral = BigDecimal.valueOf(0);
@@ -996,7 +993,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
holidayDetailDto, isHolidayValidationDone);
loan = loanTransaction.getLoan();
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
loanCollateralManagement.setLoanTransactionData(loanTransaction);
@@ -1597,7 +1594,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
businessEventNotifierService.notifyPostBusinessEvent(new LoanCloseBusinessEvent(loan));
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -1678,7 +1675,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
this.loanAccountDomainService.disableStandingInstructionsLinkedToClosedLoan(loan);
// Update loan transaction on repayment.
if (AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
if (loan.getLoanType().isIndividualAccount()) {
Set<LoanCollateralManagement> loanCollateralManagements = loan.getLoanCollateralManagements();
for (LoanCollateralManagement loanCollateralManagement : loanCollateralManagements) {
ClientCollateralManagement clientCollateralManagement = loanCollateralManagement.getClientCollateralManagement();
@@ -2486,7 +2483,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
final ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate nextPossibleRepaymentDate,
final LocalDate rescheduledRepaymentDate) {
final LocalDate actualDisbursementDate = command.localDateValueOfParameterNamed("actualDisbursementDate");
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.emiAmountParameterName);
BigDecimal emiAmount = command.bigDecimalValueOfParameterNamed(LoanApiConstants.fixedEmiAmountParameterName);
loan.regenerateScheduleOnDisbursement(scheduleGeneratorDTO, recalculateSchedule, actualDisbursementDate, emiAmount,
nextPossibleRepaymentDate, rescheduledRepaymentDate);
}
@@ -24,15 +24,12 @@ import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumb
import org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
import org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
import org.apache.fineract.infrastructure.configuration.domain.GlobalConfigurationRepositoryWrapper;
import org.apache.fineract.infrastructure.core.exception.ErrorHandler;
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
import org.apache.fineract.infrastructure.core.service.PaginationHelper;
import org.apache.fineract.infrastructure.core.service.database.DatabaseSpecificSQLGenerator;
import org.apache.fineract.infrastructure.dataqueries.service.EntityDatatableChecksWritePlatformService;
import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityRelationRepository;
import org.apache.fineract.infrastructure.entityaccess.domain.FineractEntityToEntityMappingRepository;
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext;
import org.apache.fineract.infrastructure.security.utils.ColumnValidator;
@@ -65,14 +62,12 @@ import org.apache.fineract.portfolio.delinquency.service.DelinquencyReadPlatform
import org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlatformService;
import org.apache.fineract.portfolio.fund.domain.FundRepository;
import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
import org.apache.fineract.portfolio.group.domain.GroupRepository;
import org.apache.fineract.portfolio.group.domain.GroupRepositoryWrapper;
import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
import org.apache.fineract.portfolio.loanaccount.domain.GLIMAccountInfoRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa;
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetailsRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleTransactionProcessorFactory;
@@ -82,14 +77,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanSummaryWrapper;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationRepository;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
import org.apache.fineract.portfolio.loanaccount.guarantor.service.GuarantorDomainService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AprCalculator;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleCalculationPlatformService;
import org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleHistoryWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanChargeMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanCollateralManagementMapper;
import org.apache.fineract.portfolio.loanaccount.mapper.LoanTransactionRelationMapper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationCommandFromApiJsonHelper;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationTransitionApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanApplicationValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanEventApiJsonValidator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanUpdateCommandFromApiJsonDeserializer;
@@ -118,6 +114,7 @@ import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformS
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeWritePlatformServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanDisbursementDetailsAssembler;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService;
import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerServiceImpl;
import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
@@ -131,7 +128,6 @@ import org.apache.fineract.portfolio.loanaccount.service.RecalculateInterestPost
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventService;
import org.apache.fineract.portfolio.loanaccount.service.ReplayedTransactionBusinessEventServiceImpl;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
import org.apache.fineract.portfolio.loanproduct.serialization.LoanProductDataValidator;
import org.apache.fineract.portfolio.loanproduct.service.LoanDropdownReadPlatformService;
import org.apache.fineract.portfolio.loanproduct.service.LoanProductReadPlatformService;
import org.apache.fineract.portfolio.note.domain.NoteRepository;
@@ -140,7 +136,7 @@ import org.apache.fineract.portfolio.paymenttype.service.PaymentTypeReadPlatform
import org.apache.fineract.portfolio.rate.service.RateAssembler;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecksRepository;
import org.apache.fineract.portfolio.repaymentwithpostdatedchecks.service.RepaymentWithPostDatedChecksAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountAssembler;
import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepositoryWrapper;
import org.apache.fineract.portfolio.savings.service.GSIMReadPlatformService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -168,11 +164,8 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(GLIMAccountInfoWritePlatformService.class)
public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(PlatformSecurityContext context,
GLIMAccountInfoRepository glimAccountRepository,
LoanRepository loanRepository) {
return new GLIMAccountInfoWritePlatformServiceImpl(context, glimAccountRepository, loanRepository);
public GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService(GLIMAccountInfoRepository glimAccountRepository) {
return new GLIMAccountInfoWritePlatformServiceImpl(glimAccountRepository);
}
@Bean
@@ -210,38 +203,23 @@ public class LoanAccountConfiguration {
@ConditionalOnMissingBean(LoanApplicationWritePlatformService.class)
public LoanApplicationWritePlatformService loanApplicationWritePlatformService(PlatformSecurityContext context,
FromJsonHelper fromJsonHelper, LoanApplicationTransitionApiJsonValidator loanApplicationTransitionApiJsonValidator,
LoanProductDataValidator loanProductCommandFromApiJsonDeserializer,
LoanApplicationCommandFromApiJsonHelper fromApiJsonDeserializer, LoanRepositoryWrapper loanRepositoryWrapper,
NoteRepository noteRepository, LoanScheduleCalculationPlatformService calculationPlatformService, LoanAssembler loanAssembler,
ClientRepositoryWrapper clientRepository, LoanProductRepository loanProductRepository, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler loanCollateralAssembler, AprCalculator aprCalculator, AccountNumberGenerator accountNumberGenerator,
LoanSummaryWrapper loanSummaryWrapper, GroupRepositoryWrapper groupRepository,
LoanApplicationValidator loanApplicationValidator, LoanRepositoryWrapper loanRepositoryWrapper, NoteRepository noteRepository,
LoanAssembler loanAssembler, LoanSummaryWrapper loanSummaryWrapper,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
CalendarRepository calendarRepository, CalendarInstanceRepository calendarInstanceRepository,
SavingsAccountAssembler savingsAccountAssembler, AccountAssociationsRepository accountAssociationsRepository,
LoanReadPlatformService loanReadPlatformService, AccountNumberFormatRepositoryWrapper accountNumberFormatRepository,
BusinessEventNotifierService businessEventNotifierService, ConfigurationDomainService configurationDomainService,
LoanScheduleAssembler loanScheduleAssembler, LoanUtilService loanUtilService,
CalendarReadPlatformService calendarReadPlatformService,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
GlobalConfigurationRepositoryWrapper globalConfigurationRepository,
FineractEntityToEntityMappingRepository entityMappingRepository,
FineractEntityRelationRepository fineractEntityRelationRepository,
LoanProductReadPlatformService loanProductReadPlatformService,
RateAssembler rateAssembler, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
GLIMAccountInfoRepository glimRepository, LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanProductDataValidator loanProductDataValidator) {
SavingsAccountRepositoryWrapper savingsAccountRepository, AccountAssociationsRepository accountAssociationsRepository,
LoanReadPlatformService loanReadPlatformService, BusinessEventNotifierService businessEventNotifierService,
ConfigurationDomainService configurationDomainService, LoanScheduleAssembler loanScheduleAssembler,
LoanUtilService loanUtilService, CalendarReadPlatformService calendarReadPlatformService,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService, GLIMAccountInfoRepository glimRepository,
LoanRepository loanRepository, GSIMReadPlatformService gsimReadPlatformService,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine) {
return new LoanApplicationWritePlatformServiceJpaRepositoryImpl(context, fromJsonHelper, loanApplicationTransitionApiJsonValidator,
loanProductCommandFromApiJsonDeserializer, fromApiJsonDeserializer, loanRepositoryWrapper, noteRepository,
calculationPlatformService, loanAssembler, clientRepository, loanProductRepository, loanChargeAssembler,
loanCollateralAssembler, aprCalculator, accountNumberGenerator, loanSummaryWrapper, groupRepository,
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountAssembler,
accountAssociationsRepository, loanReadPlatformService, accountNumberFormatRepository, businessEventNotifierService,
configurationDomainService, loanScheduleAssembler, loanUtilService, calendarReadPlatformService,
entityDatatableChecksWritePlatformService, globalConfigurationRepository, entityMappingRepository,
fineractEntityRelationRepository, loanProductReadPlatformService, rateAssembler, glimAccountInfoWritePlatformService,
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine, loanProductDataValidator);
loanApplicationValidator, loanRepositoryWrapper, noteRepository, loanAssembler, loanSummaryWrapper,
loanRepaymentScheduleTransactionProcessorFactory, calendarRepository, calendarInstanceRepository, savingsAccountRepository,
accountAssociationsRepository, loanReadPlatformService, businessEventNotifierService, configurationDomainService,
loanScheduleAssembler, loanUtilService, calendarReadPlatformService, entityDatatableChecksWritePlatformService,
glimRepository, loanRepository, gsimReadPlatformService, defaultLoanLifecycleStateMachine);
}
@Bean
@@ -254,18 +232,25 @@ public class LoanAccountConfiguration {
@Bean
@ConditionalOnMissingBean(LoanAssembler.class)
public LoanAssembler loanAssembler(FromJsonHelper fromApiJsonHelper, LoanRepositoryWrapper loanRepository,
LoanProductRepository loanProductRepository, ClientRepositoryWrapper clientRepository, GroupRepository groupRepository,
LoanProductRepository loanProductRepository, ClientRepositoryWrapper clientRepository, GroupRepositoryWrapper groupRepository,
FundRepository fundRepository, StaffRepository staffRepository, CodeValueRepositoryWrapper codeValueRepository,
LoanScheduleAssembler loanScheduleAssembler, LoanChargeAssembler loanChargeAssembler,
LoanCollateralAssembler collateralAssembler, LoanSummaryWrapper loanSummaryWrapper,
LoanRepaymentScheduleTransactionProcessorFactory loanRepaymentScheduleTransactionProcessorFactory,
HolidayRepository holidayRepository, ConfigurationDomainService configurationDomainService,
WorkingDaysRepositoryWrapper workingDaysRepository, LoanUtilService loanUtilService, RateAssembler rateAssembler,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory) {
WorkingDaysRepositoryWrapper workingDaysRepository, RateAssembler rateAssembler,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, ExternalIdFactory externalIdFactory,
AccountNumberFormatRepositoryWrapper accountNumberFormatRepository, GLIMAccountInfoRepository glimRepository,
AccountNumberGenerator accountNumberGenerator, GLIMAccountInfoWritePlatformService glimAccountInfoWritePlatformService,
LoanCollateralAssembler loanCollateralAssembler, LoanScheduleCalculationPlatformService calculationPlatformService,
LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler, LoanChargeMapper loanChargeMapper,
LoanCollateralManagementMapper loanCollateralManagementMapper) {
return new LoanAssembler(fromApiJsonHelper, loanRepository, loanProductRepository, clientRepository, groupRepository,
fundRepository, staffRepository, codeValueRepository, loanScheduleAssembler, loanChargeAssembler, collateralAssembler,
loanSummaryWrapper, loanRepaymentScheduleTransactionProcessorFactory, holidayRepository, configurationDomainService,
workingDaysRepository, loanUtilService, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory);
workingDaysRepository, rateAssembler, defaultLoanLifecycleStateMachine, externalIdFactory, accountNumberFormatRepository,
glimRepository, accountNumberGenerator, glimAccountInfoWritePlatformService, loanCollateralAssembler,
calculationPlatformService, loanDisbursementDetailsAssembler, loanChargeMapper, loanCollateralManagementMapper);
}
@Bean
@@ -369,10 +354,9 @@ public class LoanAccountConfiguration {
CalendarInstanceRepository calendarInstanceRepository, ConfigurationDomainService configurationDomainService,
HolidayRepository holidayRepository, WorkingDaysRepositoryWrapper workingDaysRepository,
LoanScheduleGeneratorFactory loanScheduleFactory, FloatingRatesReadPlatformService floatingRatesReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarReadPlatformService calendarReadPlatformService) {
CalendarReadPlatformService calendarReadPlatformService) {
return new LoanUtilService(applicationCurrencyRepository, calendarInstanceRepository, configurationDomainService, holidayRepository,
workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, fromApiJsonHelper,
calendarReadPlatformService);
workingDaysRepository, loanScheduleFactory, floatingRatesReadPlatformService, calendarReadPlatformService);
}
@Bean
@@ -390,15 +374,14 @@ public class LoanAccountConfiguration {
AccountAssociationsReadPlatformService accountAssociationsReadPlatformService, LoanReadPlatformService loanReadPlatformService,
FromJsonHelper fromApiJsonHelper, CalendarRepository calendarRepository,
LoanScheduleHistoryWritePlatformService loanScheduleHistoryWritePlatformService,
LoanApplicationCommandFromApiJsonHelper loanApplicationCommandFromApiJsonHelper,
AccountAssociationsRepository accountAssociationRepository, AccountTransferDetailRepository accountTransferDetailRepository,
BusinessEventNotifierService businessEventNotifierService, GuarantorDomainService guarantorDomainService,
LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
LoanApplicationValidator loanApplicationValidator, AccountAssociationsRepository accountAssociationRepository,
AccountTransferDetailRepository accountTransferDetailRepository, BusinessEventNotifierService businessEventNotifierService,
GuarantorDomainService guarantorDomainService, LoanUtilService loanUtilService, LoanSummaryWrapper loanSummaryWrapper,
EntityDatatableChecksWritePlatformService entityDatatableChecksWritePlatformService,
LoanRepaymentScheduleTransactionProcessorFactory transactionProcessingStrategy, CodeValueRepositoryWrapper codeValueRepository,
CashierTransactionDataValidator cashierTransactionDataValidator, GLIMAccountInfoRepository glimRepository,
LoanRepository loanRepository, RepaymentWithPostDatedChecksAssembler repaymentWithPostDatedChecksAssembler,
PostDatedChecksRepository postDatedChecksRepository, LoanDisbursementDetailsRepository loanDisbursementDetailsRepository,
PostDatedChecksRepository postDatedChecksRepository,
LoanRepaymentScheduleInstallmentRepository loanRepaymentScheduleInstallmentRepository,
LoanLifecycleStateMachine defaultLoanLifecycleStateMachine, LoanAccountLockService loanAccountLockService,
ExternalIdFactory externalIdFactory, ReplayedTransactionBusinessEventService replayedTransactionBusinessEventService,
@@ -410,11 +393,10 @@ public class LoanAccountConfiguration {
paymentDetailWritePlatformService, holidayRepository, configurationDomainService, workingDaysRepository,
accountTransfersWritePlatformService, accountTransfersReadPlatformService, accountAssociationsReadPlatformService,
loanReadPlatformService, fromApiJsonHelper, calendarRepository, loanScheduleHistoryWritePlatformService,
loanApplicationCommandFromApiJsonHelper, accountAssociationRepository, accountTransferDetailRepository,
businessEventNotifierService, guarantorDomainService, loanUtilService, loanSummaryWrapper,
entityDatatableChecksWritePlatformService, transactionProcessingStrategy, codeValueRepository,
cashierTransactionDataValidator, glimRepository, loanRepository, repaymentWithPostDatedChecksAssembler,
postDatedChecksRepository, loanDisbursementDetailsRepository, loanRepaymentScheduleInstallmentRepository,
loanApplicationValidator, accountAssociationRepository, accountTransferDetailRepository, businessEventNotifierService,
guarantorDomainService, loanUtilService, loanSummaryWrapper, entityDatatableChecksWritePlatformService,
transactionProcessingStrategy, codeValueRepository, cashierTransactionDataValidator, glimRepository, loanRepository,
repaymentWithPostDatedChecksAssembler, postDatedChecksRepository, loanRepaymentScheduleInstallmentRepository,
defaultLoanLifecycleStateMachine, loanAccountLockService, externalIdFactory, replayedTransactionBusinessEventService,
loanAccrualTransactionBusinessEventService, errorHandler, loanDownPaymentHandlerService);
}
@@ -439,4 +421,10 @@ public class LoanAccountConfiguration {
BusinessEventNotifierService businessEventNotifierService) {
return new LoanDownPaymentHandlerServiceImpl(loanTransactionRepository, businessEventNotifierService);
}
@Bean
@ConditionalOnMissingBean(LoanDisbursementDetailsAssembler.class)
public LoanDisbursementDetailsAssembler loanDisbursementDetailsAssembler(FromJsonHelper fromApiJsonHelper) {
return new LoanDisbursementDetailsAssembler(fromApiJsonHelper);
}
}
@@ -48,7 +48,6 @@ 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;
@@ -401,7 +400,7 @@ public final class LoanProductDataValidator {
}
// interest rates
BigDecimal interestRatePerPeriod = null;
boolean isInterestBearing = false;
if (this.fromApiJsonHelper.parameterExists(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)
&& this.fromApiJsonHelper.extractBooleanNamed(IS_LINKED_TO_FLOATING_INTEREST_RATES, element)) {
if (isEqualAmortization) {
@@ -485,6 +484,7 @@ public final class LoanProductDataValidator {
.extractBooleanNamed(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED, element);
baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode(
@@ -522,7 +522,7 @@ public final class LoanProductDataValidator {
"isFloatingInterestRateCalculationAllowed param is not supported when isLinkedToFloatingInterestRates is not supplied or false");
}
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
BigDecimal 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,10 +564,12 @@ public final class LoanProductDataValidator {
Locale.getDefault());
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
}
// Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator);
// Guarantee Funds
@@ -819,6 +821,10 @@ public final class LoanProductDataValidator {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(advancedPaymentAllocationsJsonParser
.assembleLoanProductPaymentAllocationRules(command, transactionProcessingStrategyCode));
}
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -1267,19 +1273,21 @@ public final class LoanProductDataValidator {
}
// grace validation
Integer graceOnPrincipalPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_PRINCIPAL_PAYMENT, element)) {
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT,
element);
graceOnPrincipalPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_PRINCIPAL_PAYMENT, element);
baseDataValidator.reset().parameter(GRACE_ON_PRINCIPAL_PAYMENT).value(graceOnPrincipalPayment).zeroOrPositiveAmount();
}
Integer graceOnInterestPayment = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestPayment();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_PAYMENT, element)) {
final Integer graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_PAYMENT, element);
graceOnInterestPayment = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_PAYMENT, element);
baseDataValidator.reset().parameter(GRACE_ON_INTEREST_PAYMENT).value(graceOnInterestPayment).zeroOrPositiveAmount();
}
Integer graceOnInterestCharged = loanProduct.getLoanProductRelatedDetail().getGraceOnInterestCharged();
if (this.fromApiJsonHelper.parameterExists(GRACE_ON_INTEREST_CHARGED, element)) {
final Integer graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_CHARGED, element);
graceOnInterestCharged = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(GRACE_ON_INTEREST_CHARGED, element);
baseDataValidator.reset().parameter(GRACE_ON_INTEREST_CHARGED).value(graceOnInterestCharged).zeroOrPositiveAmount();
}
@@ -1368,11 +1376,11 @@ 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);
}
boolean isInterestBearing = false;
if (isLinkedToFloatingInterestRates) {
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
@@ -1481,6 +1489,7 @@ public final class LoanProductDataValidator {
}
baseDataValidator.reset().parameter(IS_FLOATING_INTEREST_RATE_CALCULATION_ALLOWED)
.value(isFloatingInterestRateCalculationAllowed).notNull().isOneOfTheseValues(true, false);
isInterestBearing = true;
} else {
if (this.fromApiJsonHelper.parameterExists(FLOATING_RATES_ID, element)) {
baseDataValidator.reset().parameter(FLOATING_RATES_ID).failWithCode(
@@ -1536,7 +1545,7 @@ public final class LoanProductDataValidator {
baseDataValidator.reset().parameter(maxInterestRatePerPeriodParameterName).value(maxInterestRatePerPeriod).ignoreIfNull()
.zeroOrPositiveAmount();
interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
BigDecimal interestRatePerPeriod = loanProduct.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(INTEREST_RATE_PER_PERIOD, element)) {
interestRatePerPeriod = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(INTEREST_RATE_PER_PERIOD, element);
}
@@ -1549,10 +1558,11 @@ public final class LoanProductDataValidator {
}
baseDataValidator.reset().parameter(INTEREST_RATE_FREQUENCY_TYPE).value(interestRateFrequencyType).notNull().inMinMaxRange(0,
4);
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
}
// Fixed Length validation
fixedLengthValidations(transactionProcessingStrategyCode, interestRatePerPeriod, numberOfRepayments, repaymentEvery, element,
fixedLengthValidations(transactionProcessingStrategyCode, isInterestBearing, numberOfRepayments, repaymentEvery, element,
baseDataValidator);
// Guarantee Funds
@@ -1806,6 +1816,11 @@ public final class LoanProductDataValidator {
"supported.only.for.progressive.loan.schedule.handling",
"Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy");
}
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, graceOnInterestPayment,
graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
throwExceptionIfValidationWarningsExist(dataValidationErrors);
}
@@ -2400,13 +2415,7 @@ 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,
public void fixedLengthValidations(final String transactionProcessingStrategyCode, final boolean thereIsInterest,
final Integer numberOfRepayments, final Integer repayEvery, final JsonElement element,
final DataValidatorBuilder baseDataValidator) {
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.FIXED_LENGTH, element)) {
@@ -2420,15 +2429,14 @@ public final class LoanProductDataValidator {
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategyCode)) {
final String errorMsg = "Fixed Length configuration is only allowed with "
+ AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + " stratefy";
+ AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY + " strategy";
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) {
if (thereIsInterest) {
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);
throw new GeneralPlatformDomainRuleException("error.msg.fixed.length.only.supported.for.zero.interest", errorMsg);
}
final Integer valueToCompare = ((numberOfRepayments - 1) * repayEvery) + 1;
@@ -2465,4 +2473,45 @@ public final class LoanProductDataValidator {
}
}
public void validateRepaymentPeriodWithGraceSettings(final Integer numberOfRepayments, final Integer graceOnPrincipalPayment,
final Integer graceOnInterestPayment, final Integer graceOnInterestCharged, final Integer recurringMoratoriumOnPrincipalPeriods,
DataValidatorBuilder baseDataValidator) {
if (numberOfRepayments <= defaultToZeroIfNull(graceOnPrincipalPayment)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayment").value(graceOnPrincipalPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (numberOfRepayments <= defaultToZeroIfNull(graceOnInterestPayment)) {
baseDataValidator.reset().parameter("graceOnInterestPayment").value(graceOnInterestPayment)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
if (numberOfRepayments < defaultToZeroIfNull(graceOnInterestCharged)) {
baseDataValidator.reset().parameter("graceOnInterestCharged").value(graceOnInterestCharged)
.failWithCode(".mustBeLessThan.numberOfRepayments");
}
int graceOnPrincipal = 0;
if (graceOnPrincipalPayment != null) {
graceOnPrincipal = graceOnPrincipalPayment;
}
int recurMoratoriumOnPrincipal = 0;
if (recurringMoratoriumOnPrincipalPeriods != null) {
recurMoratoriumOnPrincipal = recurringMoratoriumOnPrincipalPeriods;
}
if ((recurMoratoriumOnPrincipal > 0) && ((numberOfRepayments - graceOnPrincipal) % (recurMoratoriumOnPrincipal + 1) != 1)) {
baseDataValidator.reset().parameter("graceOnPrincipalPayments.and.recurringMoratoriumOnPrincipalPeriods")
.value(graceOnPrincipal).value(recurMoratoriumOnPrincipal)
.failWithCode("causes.principal.moratorium.for.last.installment");
}
}
private Integer defaultToZeroIfNull(final Integer value) {
Integer result = value;
if (value == null) {
result = 0;
}
return result;
}
}