FINERACT-1932: Fineract modularization Fineract progressive loan module
This commit is contained in:
committed by
Adam Saghy
parent
f59ae64222
commit
53f4864980
+1358
File diff suppressed because it is too large
Load Diff
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.domain.transactionprocessor.impl;
|
||||
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Optional;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Getter
|
||||
public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
||||
|
||||
private final Optional<LoanCharge> loanCharge;
|
||||
private final Optional<LoanTransaction> loanTransaction;
|
||||
|
||||
public ChargeOrTransaction(LoanCharge loanCharge) {
|
||||
this.loanCharge = Optional.of(loanCharge);
|
||||
this.loanTransaction = Optional.empty();
|
||||
}
|
||||
|
||||
public ChargeOrTransaction(LoanTransaction loanTransaction) {
|
||||
this.loanTransaction = Optional.of(loanTransaction);
|
||||
this.loanCharge = Optional.empty();
|
||||
}
|
||||
|
||||
private LocalDate getEffectiveDate() {
|
||||
if (loanCharge.isPresent()) {
|
||||
if (isBackdatedCharge()) {
|
||||
return loanCharge.get().getDueDate();
|
||||
} else {
|
||||
return loanCharge.get().getSubmittedOnDate();
|
||||
}
|
||||
} else if (loanTransaction.isPresent()) {
|
||||
return loanTransaction.get().getTransactionDate();
|
||||
} else {
|
||||
throw new RuntimeException("Either charge or transaction should be present");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBackdatedCharge() {
|
||||
return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate());
|
||||
}
|
||||
|
||||
private LocalDate getSubmittedOnDate() {
|
||||
if (loanCharge.isPresent()) {
|
||||
return loanCharge.get().getSubmittedOnDate();
|
||||
} else if (loanTransaction.isPresent()) {
|
||||
return loanTransaction.get().getSubmittedOnDate();
|
||||
} else {
|
||||
throw new RuntimeException("Either charge or transaction should be present");
|
||||
}
|
||||
}
|
||||
|
||||
private OffsetDateTime getCreatedDateTime() {
|
||||
if (loanCharge.isPresent() && loanCharge.get().getCreatedDate().isPresent()) {
|
||||
return loanCharge.get().getCreatedDate().get();
|
||||
} else if (loanTransaction.isPresent()) {
|
||||
return loanTransaction.get().getCreatedDateTime();
|
||||
} else {
|
||||
throw new RuntimeException("Either charge with createdDate or transaction created datetime should be present");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressFBWarnings(value = "EQ_COMPARETO_USE_OBJECT_EQUALS", justification = "TODO: fix this! See: https://stackoverflow.com/questions/2609037/findbugs-how-to-solve-eq-compareto-use-object-equals")
|
||||
public int compareTo(@NotNull ChargeOrTransaction o) {
|
||||
int datePortion = this.getEffectiveDate().compareTo(o.getEffectiveDate());
|
||||
if (datePortion == 0) {
|
||||
int submittedDate = this.getSubmittedOnDate().compareTo(o.getSubmittedOnDate());
|
||||
if (submittedDate == 0) {
|
||||
return this.getCreatedDateTime().compareTo(o.getCreatedDateTime());
|
||||
}
|
||||
return submittedDate;
|
||||
}
|
||||
return datePortion;
|
||||
}
|
||||
|
||||
}
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.ApplicationCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
|
||||
import org.apache.fineract.portfolio.calendar.domain.CalendarInstance;
|
||||
import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleModelDownPaymentPeriod;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.RepaymentStartDateType;
|
||||
|
||||
public abstract class AbstractProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
|
||||
|
||||
@Override
|
||||
public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
|
||||
final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
|
||||
// TODO: handle interest calculation
|
||||
final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
|
||||
// generate list of proposed schedule due dates
|
||||
LocalDate loanEndDate = getScheduledDateGenerator().getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);
|
||||
LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations()
|
||||
.fetchLoanTermDueDateVariationsData(loanEndDate);
|
||||
if (lastDueDateVariation != null) {
|
||||
loanEndDate = lastDueDateVariation.getDateValue();
|
||||
}
|
||||
loanApplicationTerms.updateLoanEndDate(loanEndDate);
|
||||
|
||||
// determine the total charges due at time of disbursement
|
||||
final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);
|
||||
|
||||
// setup variables for tracking important facts required for loan
|
||||
// schedule generation.
|
||||
|
||||
final MonetaryCurrency currency = loanApplicationTerms.getCurrency();
|
||||
LocalDate periodStartDate = RepaymentStartDateType.DISBURSEMENT_DATE.equals(loanApplicationTerms.getRepaymentStartDateType())
|
||||
? loanApplicationTerms.getExpectedDisbursementDate()
|
||||
: loanApplicationTerms.getSubmittedOnDate();
|
||||
|
||||
LoanScheduleParams scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency,
|
||||
Money.of(currency, chargesDueAtTimeOfDisbursement), periodStartDate, Money.zero(currency));
|
||||
|
||||
List<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(loanApplicationTerms, scheduleParams,
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
|
||||
boolean isFirstRepayment = true;
|
||||
|
||||
// charges which depends on total loan interest will be added to this
|
||||
// set and handled separately after all installments generated
|
||||
final Set<LoanCharge> nonCompoundingCharges = separateTotalCompoundingPercentageCharges(loanCharges);
|
||||
boolean isNextRepaymentAvailable = true;
|
||||
|
||||
boolean thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
||||
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
||||
while (!scheduleParams.getOutstandingBalance().isZero() || thereIsDisbursementBeforeOrOnLoanEndDate) {
|
||||
scheduleParams.setActualRepaymentDate(getScheduledDateGenerator().generateNextRepaymentDate(
|
||||
scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, scheduleParams.getPeriodNumber()));
|
||||
AdjustedDateDetailsDTO adjustedDateDetailsDTO = getScheduledDateGenerator()
|
||||
.adjustRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO);
|
||||
scheduleParams.setActualRepaymentDate(adjustedDateDetailsDTO.getChangedActualRepaymentDate());
|
||||
isFirstRepayment = false;
|
||||
LocalDate scheduledDueDate = adjustedDateDetailsDTO.getChangedScheduleDate();
|
||||
|
||||
// Loan Schedule Exceptions that need to be applied for Loan Account
|
||||
LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, scheduledDueDate);
|
||||
scheduledDueDate = termVariationParams.scheduledDueDate();
|
||||
|
||||
// Updates total days in term
|
||||
scheduleParams
|
||||
.addLoanTermInDays(Math.toIntExact(ChronoUnit.DAYS.between(scheduleParams.getPeriodStartDate(), scheduledDueDate)));
|
||||
|
||||
ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, BigDecimal.ZERO);
|
||||
|
||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate);
|
||||
|
||||
// 5 determine principal,interest of repayment period
|
||||
PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(loanApplicationTerms,
|
||||
scheduleParams, mc);
|
||||
|
||||
// update cumulative fields for principal
|
||||
currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal());
|
||||
updateOutstandingBalance(scheduleParams, currentPeriodParams);
|
||||
|
||||
if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) {
|
||||
currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance());
|
||||
scheduleParams.setOutstandingBalance(Money.zero(currency));
|
||||
}
|
||||
|
||||
if (!isNextRepaymentAvailable) {
|
||||
scheduleParams.getDisburseDetailMap().clear();
|
||||
}
|
||||
|
||||
// applies charges for the period
|
||||
applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams, mc);
|
||||
|
||||
// sum up real totalInstallmentDue from components
|
||||
final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod();
|
||||
|
||||
// if previous installment is last then add interest to same
|
||||
// installment
|
||||
if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) {
|
||||
currentPeriodParams.getLastInstallment().addInterestAmount(currentPeriodParams.getInterestForThisPeriod());
|
||||
continue;
|
||||
}
|
||||
|
||||
// create repayment period from parts
|
||||
LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(scheduleParams.getInstalmentNumber(),
|
||||
scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(),
|
||||
currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(),
|
||||
totalInstallmentDue, false);
|
||||
if (principalInterestForThisPeriod.getRescheduleInterestPortion() != null) {
|
||||
installment.setRescheduleInterestPortion(principalInterestForThisPeriod.getRescheduleInterestPortion().getAmount());
|
||||
}
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
if (loanApplicationTerms.getCurrentPeriodFixedEmiAmount() != null) {
|
||||
installment.setEMIFixedSpecificToInstallmentTrue();
|
||||
}
|
||||
|
||||
periods.add(installment);
|
||||
|
||||
// Updates principal paid map with effective date for reducing
|
||||
// the amount from outstanding balance(interest calculation)
|
||||
updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams,
|
||||
installment);
|
||||
|
||||
// handle cumulative fields
|
||||
|
||||
scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod());
|
||||
scheduleParams.addTotalRepaymentExpected(totalInstallmentDue);
|
||||
scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod());
|
||||
scheduleParams.setPeriodStartDate(scheduledDueDate);
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.incrementPeriodNumber();
|
||||
// if (termVariationParams.isRecalculateAmounts()) {
|
||||
// loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null);
|
||||
// loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null);
|
||||
// adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(),
|
||||
// scheduleParams.getPeriodNumber(), mc);
|
||||
// }
|
||||
thereIsDisbursementBeforeOrOnLoanEndDate = scheduleParams.getDisburseDetailMap().entrySet().stream()
|
||||
.anyMatch(d -> !d.getKey().isAfter(loanApplicationTerms.getLoanEndDate()));
|
||||
}
|
||||
|
||||
// If the disbursement happened after maturity date
|
||||
if (loanApplicationTerms.isMultiDisburseLoan()) {
|
||||
processDisbursements(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods,
|
||||
DateUtils.getBusinessLocalDate().plusDays(1));
|
||||
}
|
||||
|
||||
// determine fees and penalties for charges which depends on total
|
||||
// loan interest
|
||||
updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges, mc);
|
||||
|
||||
final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
|
||||
final BigDecimal totalOutstanding = BigDecimal.ZERO;
|
||||
|
||||
return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(),
|
||||
scheduleParams.getPrincipalToBeScheduled().plus(loanApplicationTerms.getDownPaymentAmount()),
|
||||
scheduleParams.getTotalCumulativePrincipal().plus(loanApplicationTerms.getDownPaymentAmount()).getAmount(),
|
||||
totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(),
|
||||
scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(),
|
||||
scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanScheduleDTO rescheduleNextInstallments(MathContext mc, LoanApplicationTerms loanApplicationTerms, Loan loan,
|
||||
HolidayDetailDTO holidayDetailDTO, LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
|
||||
LocalDate rescheduleFrom) {
|
||||
|
||||
LoanScheduleModel model = generate(mc, loanApplicationTerms, loan.getActiveCharges(), holidayDetailDTO);
|
||||
return LoanScheduleDTO.from(null, model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(MonetaryCurrency currency, LocalDate onDate,
|
||||
LoanApplicationTerms loanApplicationTerms, MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Abstract methods
|
||||
public abstract ScheduledDateGenerator getScheduledDateGenerator();
|
||||
|
||||
public abstract PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator();
|
||||
|
||||
public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams loanScheduleParams, MathContext mc);
|
||||
|
||||
// Private, internal methods
|
||||
private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
|
||||
BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isDueAtDisbursement()) {
|
||||
chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
|
||||
}
|
||||
}
|
||||
return chargesDueAtTimeOfDisbursement;
|
||||
}
|
||||
|
||||
private List<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams loanScheduleParams, final BigDecimal chargesDueAtTimeOfDisbursement) {
|
||||
List<LoanScheduleModelPeriod> periods = new ArrayList<>();
|
||||
|
||||
// In case of `disallowExpectedDisbursementDetails = true`, anyway at least 1 disbursement details must exist
|
||||
if (loanApplicationTerms.getDisbursementDatas().isEmpty()) {
|
||||
loanApplicationTerms.getDisbursementDatas()
|
||||
.add(new DisbursementData(1L, loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms.getPrincipal().getAmount(), null, null,
|
||||
null, null));
|
||||
}
|
||||
for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
|
||||
Money principalMoney = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal());
|
||||
if (disbursementData.disbursementDate().equals(loanScheduleParams.getPeriodStartDate())) {
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod.disbursement(
|
||||
disbursementData.disbursementDate(), Money.of(loanScheduleParams.getCurrency(), disbursementData.getPrincipal()),
|
||||
chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
loanScheduleParams.addOutstandingBalance(principalMoney);
|
||||
loanScheduleParams.addOutstandingBalanceAsPerRest(principalMoney);
|
||||
loanScheduleParams.addPrincipalToBeScheduled(principalMoney);
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(principalMoney));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
loanScheduleParams, loanApplicationTerms.getExpectedDisbursementDate(), disbursementData.getPrincipal());
|
||||
periods.add(downPaymentPeriod);
|
||||
}
|
||||
} else {
|
||||
Money disbursedAmount = loanScheduleParams.getDisburseDetailMap().getOrDefault(disbursementData.disbursementDate(),
|
||||
Money.zero(loanApplicationTerms.getCurrency()));
|
||||
loanScheduleParams.getDisburseDetailMap().put(disbursementData.disbursementDate(), disbursedAmount.add(principalMoney));
|
||||
}
|
||||
}
|
||||
|
||||
return periods;
|
||||
}
|
||||
|
||||
private LoanScheduleModelDownPaymentPeriod createDownPaymentPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams scheduleParams, LocalDate date, BigDecimal periodBaseAmount) {
|
||||
Money downPaymentAmount = Money.of(loanApplicationTerms.getCurrency(),
|
||||
MathUtil.percentageOf(periodBaseAmount, loanApplicationTerms.getDisbursedAmountPercentageForDownPayment(), 19));
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
downPaymentAmount = Money.roundToMultiplesOf(downPaymentAmount, loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
LoanScheduleModelDownPaymentPeriod installment = LoanScheduleModelDownPaymentPeriod.downPayment(
|
||||
scheduleParams.getInstalmentNumber(), date, downPaymentAmount,
|
||||
scheduleParams.getOutstandingBalance().minus(downPaymentAmount));
|
||||
|
||||
addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment);
|
||||
|
||||
scheduleParams.incrementInstalmentNumber();
|
||||
scheduleParams.addTotalRepaymentExpected(downPaymentAmount);
|
||||
scheduleParams.reduceOutstandingBalance(downPaymentAmount);
|
||||
scheduleParams.reduceOutstandingBalanceAsPerRest(downPaymentAmount);
|
||||
scheduleParams.setPrincipalToBeScheduled(scheduleParams.getPrincipalToBeScheduled().minus(downPaymentAmount));
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().minus(downPaymentAmount));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
return installment;
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
|
||||
final LoanScheduleModelPeriod scheduledLoanInstallment) {
|
||||
LoanRepaymentScheduleInstallment installment = null;
|
||||
if (scheduledLoanInstallment.isRepaymentPeriod() || scheduledLoanInstallment.isDownPaymentPeriod()) {
|
||||
installment = new LoanRepaymentScheduleInstallment(null, scheduledLoanInstallment.periodNumber(),
|
||||
scheduledLoanInstallment.periodFromDate(), scheduledLoanInstallment.periodDueDate(),
|
||||
scheduledLoanInstallment.principalDue(), scheduledLoanInstallment.interestDue(),
|
||||
scheduledLoanInstallment.feeChargesDue(), scheduledLoanInstallment.penaltyChargesDue(),
|
||||
scheduledLoanInstallment.isRecalculatedInterestComponent(), scheduledLoanInstallment.getLoanCompoundingDetails(),
|
||||
scheduledLoanInstallment.rescheduleInterestPortion(), scheduledLoanInstallment.isDownPaymentPeriod());
|
||||
installments.add(installment);
|
||||
}
|
||||
return installment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Method add extra disbursement periods (if applicable) and update the schedule params
|
||||
*/
|
||||
private void processDisbursements(final LoanApplicationTerms loanApplicationTerms, final BigDecimal chargesDueAtTimeOfDisbursement,
|
||||
LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final LocalDate scheduledDueDate) {
|
||||
Iterator<Map.Entry<LocalDate, Money>> iter = scheduleParams.getDisburseDetailMap().entrySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
Map.Entry<LocalDate, Money> disburseDetail = iter.next();
|
||||
if ((disburseDetail.getKey().isEqual(scheduleParams.getPeriodStartDate())
|
||||
|| disburseDetail.getKey().isAfter(scheduleParams.getPeriodStartDate()))
|
||||
&& disburseDetail.getKey().isBefore(scheduledDueDate)) {
|
||||
// validation check for amount not exceeds specified max
|
||||
// amount as per the configuration
|
||||
if (loanApplicationTerms.isMultiDisburseLoan() && loanApplicationTerms.getMaxOutstandingBalance() != null) {
|
||||
Money maxOutstandingBalance = loanApplicationTerms.getMaxOutstandingBalanceMoney();
|
||||
if (scheduleParams.getOutstandingBalance().plus(disburseDetail.getValue()).isGreaterThan(maxOutstandingBalance)) {
|
||||
String errorMsg = "Outstanding balance must not exceed the amount: " + maxOutstandingBalance;
|
||||
throw new MultiDisbursementOutstandingAmoutException(errorMsg, loanApplicationTerms.getMaxOutstandingBalance(),
|
||||
disburseDetail.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
// creates and add disbursement detail to the repayments
|
||||
// period
|
||||
final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
|
||||
.disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement);
|
||||
periods.add(disbursementPeriod);
|
||||
|
||||
BigDecimal downPaymentAmt = BigDecimal.ZERO;
|
||||
// updates actual outstanding balance with new
|
||||
// disbursement detail
|
||||
scheduleParams.addOutstandingBalance(disburseDetail.getValue());
|
||||
scheduleParams.addOutstandingBalanceAsPerRest(disburseDetail.getValue());
|
||||
scheduleParams.addPrincipalToBeScheduled(disburseDetail.getValue());
|
||||
loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
|
||||
loanApplicationTerms.resetFixedEmiAmount();
|
||||
if (loanApplicationTerms.isDownPaymentEnabled()) {
|
||||
// get list of disbursements done on same day and create down payment periods
|
||||
List<DisbursementData> disbursementsOnSameDate = loanApplicationTerms.getDisbursementDatas().stream()
|
||||
.filter(disbursementData -> DateUtils.isEqual(disbursementData.disbursementDate(), disburseDetail.getKey()))
|
||||
.toList();
|
||||
for (DisbursementData disbursementData : disbursementsOnSameDate) {
|
||||
final LoanScheduleModelDownPaymentPeriod downPaymentPeriod = createDownPaymentPeriod(loanApplicationTerms,
|
||||
scheduleParams, disbursementData.disbursementDate(), disbursementData.getPrincipal());
|
||||
periods.add(downPaymentPeriod);
|
||||
downPaymentAmt = downPaymentAmt.add(downPaymentPeriod.principalDue());
|
||||
}
|
||||
}
|
||||
iter.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyChargesForCurrentPeriod(final Set<LoanCharge> loanCharges, final MonetaryCurrency currency,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
final MathContext mc) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(currentPeriodParams.getPrincipalForThisPeriod(),
|
||||
currentPeriodParams.getInterestForThisPeriod(), null);
|
||||
currentPeriodParams.setFeeChargesForInstallment(cumulativeFeeChargesDueWithin(scheduleParams.getPeriodStartDate(), scheduledDueDate,
|
||||
loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
currentPeriodParams.setPenaltyChargesForInstallment(cumulativePenaltyChargesDueWithin(scheduleParams.getPeriodStartDate(),
|
||||
scheduledDueDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(),
|
||||
scheduleParams.getTotalCumulativeInterest(), true, scheduleParams.isFirstPeriod(), mc));
|
||||
scheduleParams.addTotalFeeChargesCharged(currentPeriodParams.getFeeChargesForInstallment());
|
||||
scheduleParams.addTotalPenaltyChargesCharged(currentPeriodParams.getPenaltyChargesForInstallment());
|
||||
}
|
||||
|
||||
private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd, final Set<LoanCharge> loanCharges,
|
||||
final MonetaryCurrency monetaryCurrency, final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isFeeCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money getCumulativeAmountOfCharge(LocalDate periodStart, LocalDate periodEnd, PrincipalInterest principalInterestForThisPeriod,
|
||||
Money principalDisbursed, Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable,
|
||||
boolean isFirstPeriod, LoanCharge loanCharge, Money cumulative, MathContext mc) {
|
||||
boolean isDue = isFirstPeriod ? loanCharge.isDueForCollectionFromIncludingAndUpToAndIncluding(periodStart, periodEnd)
|
||||
: loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd);
|
||||
if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
|
||||
cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, cumulative, loanCharge, mc);
|
||||
} else if (loanCharge.isOverdueInstallmentCharge() && isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = cumulative.plus(loanCharge.chargeAmount());
|
||||
} else if (isDue && loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed, totalInterestChargedForFullLoanTerm, cumulative,
|
||||
loanCharge, mc);
|
||||
} else if (isDue) {
|
||||
cumulative = cumulative.plus(loanCharge.amount());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
|
||||
final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
|
||||
final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, boolean isInstallmentChargeApplicable, final boolean isFirstPeriod,
|
||||
final MathContext mc) {
|
||||
Money cumulative = Money.zero(monetaryCurrency);
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
cumulative = getCumulativeAmountOfCharge(periodStart, periodEnd, principalInterestForThisPeriod, principalDisbursed,
|
||||
totalInterestChargedForFullLoanTerm, isInstallmentChargeApplicable, isFirstPeriod, loanCharge, cumulative, mc);
|
||||
}
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod, Money cumulative,
|
||||
final LoanCharge loanCharge, final MathContext mc) {
|
||||
if (loanCharge.getChargeCalculation().isPercentageBased()) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
|
||||
.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
} else {
|
||||
cumulative = cumulative.plus(loanCharge.amountOrPercentage());
|
||||
}
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
|
||||
final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge, final MathContext mc) {
|
||||
BigDecimal amount = BigDecimal.ZERO;
|
||||
if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
|
||||
amount = amount.add(principalDisbursed.getAmount()).add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
|
||||
amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
|
||||
} else {
|
||||
amount = amount.add(principalDisbursed.getAmount());
|
||||
}
|
||||
BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100), mc);
|
||||
cumulative = cumulative.plus(loanChargeAmt);
|
||||
return cumulative;
|
||||
}
|
||||
|
||||
private void updateAmountsWithEffectiveDate(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
|
||||
LoanScheduleParams scheduleParams, LocalDate scheduledDueDate, ScheduleCurrentPeriodParams currentPeriodParams,
|
||||
LoanScheduleModelPeriod installment) {
|
||||
LocalDate amountApplicableDate = installment.periodDueDate();
|
||||
if (loanApplicationTerms.isInterestRecalculationEnabled()) {
|
||||
amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1), loanApplicationTerms,
|
||||
holidayDetailDTO);
|
||||
}
|
||||
updateMapWithAmount(scheduleParams.getPrincipalPortionMap(),
|
||||
currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()), amountApplicableDate);
|
||||
|
||||
// update outstanding balance for interest calculation
|
||||
updateOutstandingBalanceAsPerRest(scheduleParams, scheduledDueDate);
|
||||
}
|
||||
|
||||
private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
|
||||
final HolidayDetailDTO holidayDetailDTO) {
|
||||
LocalDate nextScheduleDate;
|
||||
if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
|
||||
nextScheduleDate = getScheduledDateGenerator().generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(startDate,
|
||||
loanApplicationTerms, holidayDetailDTO);
|
||||
} else {
|
||||
CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
|
||||
nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
|
||||
}
|
||||
|
||||
return nextScheduleDate;
|
||||
}
|
||||
|
||||
private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount, final LocalDate amountApplicableDate) {
|
||||
Money principalPaid = amount;
|
||||
if (map.containsKey(amountApplicableDate)) {
|
||||
principalPaid = map.get(amountApplicableDate).plus(principalPaid);
|
||||
}
|
||||
map.put(amountApplicableDate, principalPaid);
|
||||
|
||||
}
|
||||
|
||||
private void updateOutstandingBalanceAsPerRest(final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
|
||||
scheduleParams.setOutstandingBalanceAsPerRest(updateBalanceForInterestCalculation(scheduleParams.getPrincipalPortionMap(),
|
||||
scheduledDueDate, scheduleParams.getOutstandingBalanceAsPerRest(), false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies all the past date principal changes and apply them on outstanding balance for future calculations
|
||||
*/
|
||||
private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap, final LocalDate scheduledDueDate,
|
||||
final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
|
||||
List<LocalDate> removeFromPrincipalPortionMap = new ArrayList<>();
|
||||
Money outstandingBalance = outstandingBalanceAsPerRest;
|
||||
for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
|
||||
if (!principal.getKey().isAfter(scheduledDueDate)) {
|
||||
if (addMapDetails) {
|
||||
outstandingBalance = outstandingBalance.plus(principal.getValue());
|
||||
} else {
|
||||
outstandingBalance = outstandingBalance.minus(principal.getValue());
|
||||
}
|
||||
removeFromPrincipalPortionMap.add(principal.getKey());
|
||||
}
|
||||
}
|
||||
for (LocalDate date : removeFromPrincipalPortionMap) {
|
||||
principalPortionMap.remove(date);
|
||||
}
|
||||
return outstandingBalance;
|
||||
}
|
||||
|
||||
private void updatePeriodsWithCharges(final MonetaryCurrency currency, LoanScheduleParams scheduleParams,
|
||||
final Collection<LoanScheduleModelPeriod> periods, final Set<LoanCharge> nonCompoundingCharges, MathContext mc) {
|
||||
for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
|
||||
if (loanScheduleModelPeriod.isRepaymentPeriod()) {
|
||||
PrincipalInterest principalInterest = new PrincipalInterest(Money.of(currency, loanScheduleModelPeriod.principalDue()),
|
||||
Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
|
||||
Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(loanScheduleModelPeriod.periodFromDate(),
|
||||
loanScheduleModelPeriod.periodDueDate(), nonCompoundingCharges, currency, principalInterest,
|
||||
scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(),
|
||||
!loanScheduleModelPeriod.isRecalculatedInterestComponent(), scheduleParams.isFirstPeriod(), mc);
|
||||
scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment);
|
||||
scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment);
|
||||
scheduleParams.addTotalRepaymentExpected(feeChargesForInstallment.plus(penaltyChargesForInstallment));
|
||||
loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(), penaltyChargesForInstallment.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<LoanCharge> separateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
|
||||
Set<LoanCharge> interestCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
|
||||
|| loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
|
||||
interestCharges.add(loanCharge);
|
||||
}
|
||||
}
|
||||
loanCharges.removeAll(interestCharges);
|
||||
return interestCharges;
|
||||
}
|
||||
|
||||
private void updateOutstandingBalance(LoanScheduleParams scheduleParams, ScheduleCurrentPeriodParams currentPeriodParams) {
|
||||
// update outstandingLoanBlance using current period
|
||||
// 'principalDue'
|
||||
scheduleParams
|
||||
.reduceOutstandingBalance(currentPeriodParams.getPrincipalForThisPeriod().minus(currentPeriodParams.getReducedBalance()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param loanApplicationTerms
|
||||
* @param scheduleParams
|
||||
* @param scheduledDueDate
|
||||
* @return
|
||||
*/
|
||||
private LoanTermVariationParams applyLoanTermVariations(final LoanApplicationTerms loanApplicationTerms,
|
||||
final LoanScheduleParams scheduleParams, final LocalDate scheduledDueDate) {
|
||||
boolean skipPeriod = false;
|
||||
boolean recalculateAmounts = false;
|
||||
LocalDate modifiedScheduledDueDate = scheduledDueDate;
|
||||
ArrayList<LoanTermVariationsData> variationsData = null;
|
||||
|
||||
// due date changes should be applied only for that dueDate
|
||||
if (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(scheduledDueDate)) {
|
||||
LoanTermVariationsData loanTermVariationsData = loanApplicationTerms.getLoanTermVariations().nextDueDateVariation();
|
||||
if (DateUtils.isEqual(modifiedScheduledDueDate, loanTermVariationsData.getTermVariationApplicableFrom())) {
|
||||
modifiedScheduledDueDate = loanTermVariationsData.getDateValue();
|
||||
loanApplicationTerms.updateVariationDays(DateUtils.getDifferenceInDays(scheduledDueDate, modifiedScheduledDueDate));
|
||||
if (!loanTermVariationsData.isSpecificToInstallment()) {
|
||||
scheduleParams.setActualRepaymentDate(modifiedScheduledDueDate);
|
||||
loanApplicationTerms.setNewScheduledDueDateStart(modifiedScheduledDueDate);
|
||||
}
|
||||
loanTermVariationsData.setProcessed(true);
|
||||
}
|
||||
}
|
||||
|
||||
return new LoanTermVariationParams(skipPeriod, recalculateAmounts, modifiedScheduledDueDate, variationsData);
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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.loanschedule.domain;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ProgressiveLoanScheduleGenerator extends AbstractProgressiveLoanScheduleGenerator {
|
||||
|
||||
private final ScheduledDateGenerator scheduledDateGenerator;
|
||||
private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator;
|
||||
|
||||
@Override
|
||||
public ScheduledDateGenerator getScheduledDateGenerator() {
|
||||
return scheduledDateGenerator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaymentPeriodsInOneYearCalculator getPaymentPeriodsInOneYearCalculator() {
|
||||
return paymentPeriodsInOneYearCalculator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrincipalInterest calculatePrincipalInterestComponentsForPeriod(LoanApplicationTerms loanApplicationTerms,
|
||||
LoanScheduleParams loanScheduleParams, MathContext mc) {
|
||||
// TODO: handle interest calculation
|
||||
int periodNumber = loanScheduleParams.getPeriodNumber();
|
||||
BigDecimal fixedEMIAmount = loanApplicationTerms.getFixedEmiAmount();
|
||||
Money calculatedPrincipal;
|
||||
if (fixedEMIAmount == null) {
|
||||
int noRemainingPeriod = loanApplicationTerms.getActualNoOfRepaymnets() - periodNumber + 1;
|
||||
calculatedPrincipal = loanScheduleParams.getOutstandingBalance().dividedBy(noRemainingPeriod, mc.getRoundingMode());
|
||||
if (loanApplicationTerms.getInstallmentAmountInMultiplesOf() != null) {
|
||||
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal,
|
||||
loanApplicationTerms.getInstallmentAmountInMultiplesOf());
|
||||
}
|
||||
loanApplicationTerms.setFixedEmiAmount(calculatedPrincipal.getAmount());
|
||||
} else {
|
||||
calculatedPrincipal = Money.of(loanApplicationTerms.getCurrency(), fixedEMIAmount);
|
||||
}
|
||||
// adjust if needed
|
||||
if (periodNumber == loanApplicationTerms.getActualNoOfRepaymnets()) {
|
||||
Money remainingAmount = loanScheduleParams.getOutstandingBalance().minus(calculatedPrincipal);
|
||||
calculatedPrincipal = calculatedPrincipal.plus(remainingAmount);
|
||||
}
|
||||
|
||||
return new PrincipalInterest(calculatedPrincipal, Money.zero(loanApplicationTerms.getCurrency()),
|
||||
Money.zero(loanApplicationTerms.getCurrency()));
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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.starter;
|
||||
|
||||
import org.apache.fineract.infrastructure.core.condition.PropertiesCondition;
|
||||
import org.apache.fineract.infrastructure.core.config.FineractProperties;
|
||||
|
||||
public class AdvancedPaymentScheduleTransactionProcessorCondition extends PropertiesCondition {
|
||||
|
||||
@Override
|
||||
protected boolean matches(FineractProperties properties) {
|
||||
return properties.getLoan().getTransactionProcessor().getAdvancedPaymentStrategy().isEnabled();
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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.loanproduct.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class AdvancedPaymentData implements Serializable {
|
||||
|
||||
private final String transactionType;
|
||||
private final String futureInstallmentAllocationRule;
|
||||
private final List<PaymentAllocationOrder> paymentAllocationOrder;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class PaymentAllocationOrder implements Serializable {
|
||||
|
||||
private final String paymentAllocationRule;
|
||||
private final Integer order;
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanproduct.data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class CreditAllocationData implements Serializable {
|
||||
|
||||
private final String transactionType;
|
||||
private final List<CreditAllocationOrder> creditAllocationOrder;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public static class CreditAllocationOrder implements Serializable {
|
||||
|
||||
private final String creditAllocationRule;
|
||||
private final Integer order;
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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.loanproduct.domain;
|
||||
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class AdvancedPaymentAllocationsJsonParser {
|
||||
|
||||
public final AdvancedPaymentAllocationsValidator advancedPaymentAllocationsValidator;
|
||||
|
||||
public List<LoanProductPaymentAllocationRule> assembleLoanProductPaymentAllocationRules(final JsonCommand command,
|
||||
String loanTransactionProcessingStrategyCode) {
|
||||
JsonArray paymentAllocations = command.arrayOfParameterNamed("paymentAllocation");
|
||||
List<LoanProductPaymentAllocationRule> productPaymentAllocationRules = null;
|
||||
if (paymentAllocations != null) {
|
||||
productPaymentAllocationRules = paymentAllocations.asList().stream().map(json -> {
|
||||
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
|
||||
LoanProductPaymentAllocationRule loanProductPaymentAllocationRule = new LoanProductPaymentAllocationRule();
|
||||
populatePaymentAllocationRules(map, loanProductPaymentAllocationRule);
|
||||
populateFutureInstallment(map, loanProductPaymentAllocationRule);
|
||||
populateTransactionType(map, loanProductPaymentAllocationRule);
|
||||
return loanProductPaymentAllocationRule;
|
||||
}).toList();
|
||||
}
|
||||
advancedPaymentAllocationsValidator.validate(productPaymentAllocationRules, loanTransactionProcessingStrategyCode);
|
||||
return productPaymentAllocationRules;
|
||||
}
|
||||
|
||||
private void populatePaymentAllocationRules(Map<String, JsonElement> map,
|
||||
LoanProductPaymentAllocationRule loanProductPaymentAllocationRule) {
|
||||
JsonArray paymentAllocationOrder = asJsonArrayOrNull(map.get("paymentAllocationOrder"));
|
||||
if (paymentAllocationOrder != null) {
|
||||
loanProductPaymentAllocationRule.setAllocationTypes(getPaymentAllocationTypes(paymentAllocationOrder));
|
||||
}
|
||||
}
|
||||
|
||||
private void populateFutureInstallment(Map<String, JsonElement> map,
|
||||
LoanProductPaymentAllocationRule loanProductPaymentAllocationRule) {
|
||||
String futureInstallmentAllocationRule = asStringOrNull(map.get("futureInstallmentAllocationRule"));
|
||||
if (futureInstallmentAllocationRule != null) {
|
||||
loanProductPaymentAllocationRule.setFutureInstallmentAllocationRule(
|
||||
Enums.getIfPresent(FutureInstallmentAllocationRule.class, futureInstallmentAllocationRule).orNull());
|
||||
}
|
||||
}
|
||||
|
||||
private void populateTransactionType(Map<String, JsonElement> map, LoanProductPaymentAllocationRule loanProductPaymentAllocationRule) {
|
||||
String transactionType = asStringOrNull(map.get("transactionType"));
|
||||
if (transactionType != null) {
|
||||
loanProductPaymentAllocationRule
|
||||
.setTransactionType(Enums.getIfPresent(PaymentAllocationTransactionType.class, transactionType).orNull());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<PaymentAllocationType> getPaymentAllocationTypes(JsonArray paymentAllocationOrder) {
|
||||
if (paymentAllocationOrder != null) {
|
||||
List<Pair<Integer, PaymentAllocationType>> parsedListWithOrder = paymentAllocationOrder.asList().stream().map(json -> {
|
||||
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
|
||||
PaymentAllocationType paymentAllocationType = null;
|
||||
String paymentAllocationRule = asStringOrNull(map.get("paymentAllocationRule"));
|
||||
if (paymentAllocationRule != null) {
|
||||
paymentAllocationType = Enums.getIfPresent(PaymentAllocationType.class, paymentAllocationRule).orNull();
|
||||
}
|
||||
return Pair.of(asIntegerOrNull(map.get("order")), paymentAllocationType);
|
||||
}).sorted(Comparator.comparing(Pair::getLeft)).toList();
|
||||
advancedPaymentAllocationsValidator.validatePairOfOrderAndPaymentAllocationType(parsedListWithOrder);
|
||||
return parsedListWithOrder.stream().map(Pair::getRight).toList();
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private Integer asIntegerOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsInt();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String asStringOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JsonArray asJsonArrayOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsJsonArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
/**
|
||||
* 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.loanproduct.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.IntStream;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class AdvancedPaymentAllocationsValidator {
|
||||
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||
|
||||
public void validate(List<LoanProductPaymentAllocationRule> rules, String code) {
|
||||
if (isAdvancedPaymentStrategy(code)) {
|
||||
if (!hasLoanProductPaymentAllocationRule(rules) || !hasAtLeastOneDefaultPaymentAllocation(rules)) {
|
||||
raiseValidationError("advanced-payment-strategy-without-default-payment-allocation",
|
||||
"Advanced-payment-allocation-strategy was selected but no DEFAULT payment allocation was provided");
|
||||
}
|
||||
|
||||
if (hasDuplicateTransactionTypes(rules)) {
|
||||
raiseValidationError("advanced-payment-strategy-with-duplicate-payment-allocation",
|
||||
"The same transaction type must be provided only once");
|
||||
}
|
||||
|
||||
for (LoanProductPaymentAllocationRule rule : rules) {
|
||||
validateAllocationRule(rule);
|
||||
}
|
||||
|
||||
} else {
|
||||
if (hasLoanProductPaymentAllocationRule(rules)) {
|
||||
raiseValidationError("payment_allocation.must.not.be.provided.when.allocation.strategy.is.not.advanced-payment-strategy",
|
||||
"In case '" + code + "' payment strategy, payment_allocation must not be provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validatePairOfOrderAndPaymentAllocationType(List<Pair<Integer, PaymentAllocationType>> rules) {
|
||||
if (rules.size() != 12) {
|
||||
raiseValidationError("advanced-payment-strategy.each_payment_allocation_order.must.contain.12.entries",
|
||||
"Each provided payment allocation must contain exactly 12 allocation rules, but " + rules.size() + " were provided");
|
||||
}
|
||||
|
||||
List<PaymentAllocationType> deduped = rules.stream().map(Pair::getRight).distinct().toList();
|
||||
if (deduped.size() != 12) {
|
||||
raiseValidationError("advanced-payment-strategy.must.not.have.duplicate.payment.allocation.rule",
|
||||
"The list of provided payment allocation rules must not contain any duplicates");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(IntStream.rangeClosed(1, 12).boxed().toArray(), rules.stream().map(Pair::getLeft).toArray())) {
|
||||
raiseValidationError("advanced-payment-strategy.invalid.order", "The provided orders must be between 1 and 12");
|
||||
}
|
||||
}
|
||||
|
||||
public void checkGroupingOfAllocationRules(List<LoanProductPaymentAllocationRule> loanProductPaymentAllocationRules) {
|
||||
loanProductPaymentAllocationRules.forEach(paymentAllocationRule -> {
|
||||
AtomicInteger pastDueRuleInteger = new AtomicInteger();
|
||||
AtomicInteger dueRuleInteger = new AtomicInteger();
|
||||
AtomicInteger inAdvanceRuleInteger = new AtomicInteger();
|
||||
paymentAllocationRule.getAllocationTypes().forEach(paymentAllocationType -> {
|
||||
validateAllocationType(paymentAllocationType, pastDueRuleInteger, dueRuleInteger, inAdvanceRuleInteger);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void validateAllocationType(PaymentAllocationType paymentAllocationType, AtomicInteger pastDueRuleInteger,
|
||||
AtomicInteger dueRuleInteger, AtomicInteger inAdvanceRuleInteger) {
|
||||
switch (paymentAllocationType.getDueType()) {
|
||||
case PAST_DUE -> validateDueType(pastDueRuleInteger, dueRuleInteger, inAdvanceRuleInteger);
|
||||
case DUE -> validateDueType(dueRuleInteger, pastDueRuleInteger, inAdvanceRuleInteger);
|
||||
case IN_ADVANCE -> validateDueType(inAdvanceRuleInteger, pastDueRuleInteger, dueRuleInteger);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDueType(AtomicInteger currentRuleInteger, AtomicInteger otherRule1Integer, AtomicInteger otherRule2Integer) {
|
||||
currentRuleInteger.incrementAndGet();
|
||||
if ((otherRule1Integer.get() > 0 && otherRule1Integer.get() < 4) || (otherRule2Integer.get() > 0 && otherRule2Integer.get() < 4)) {
|
||||
raiseValidationError("mixed.due.type.allocation.rules.are.not.supported.with.horizontal.installment.processing",
|
||||
"Horizontal repayment schedule processing is not supporting mixed due type allocation rules!");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDuplicateTransactionTypes(List<LoanProductPaymentAllocationRule> rules) {
|
||||
return rules != null
|
||||
&& rules.stream().map(LoanProductPaymentAllocationRule::getTransactionType).distinct().toList().size() != rules.size();
|
||||
}
|
||||
|
||||
private void validateAllocationRule(LoanProductPaymentAllocationRule rule) {
|
||||
if (rule.getTransactionType() == null) {
|
||||
raiseValidationError("advanced-payment-strategy.with.not.valid.transaction.type",
|
||||
"Payment allocation was provided with a not valid transaction type");
|
||||
}
|
||||
if (rule.getFutureInstallmentAllocationRule() == null) {
|
||||
raiseValidationError("advanced-payment-strategy.with.not.valid.future.installment.allocation.rule",
|
||||
"Payment allocation was provided without a valid future installment allocation rule");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdvancedPaymentStrategy(String code) {
|
||||
return ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(code);
|
||||
}
|
||||
|
||||
private boolean hasAtLeastOneDefaultPaymentAllocation(List<LoanProductPaymentAllocationRule> rules) {
|
||||
return rules != null && rules.stream() //
|
||||
.filter(r -> PaymentAllocationTransactionType.DEFAULT.equals(r.getTransactionType())) //
|
||||
.toList() //
|
||||
.size() > 0;
|
||||
}
|
||||
|
||||
private boolean hasLoanProductPaymentAllocationRule(List<LoanProductPaymentAllocationRule> rules) {
|
||||
return rules != null && rules.size() > 0;
|
||||
}
|
||||
|
||||
private void raiseValidationError(String globalisationMessageCode, String msg) {
|
||||
throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError(globalisationMessageCode, msg)));
|
||||
}
|
||||
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* 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.loanproduct.domain;
|
||||
|
||||
import com.google.common.base.Enums;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import lombok.AllArgsConstructor;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@AllArgsConstructor
|
||||
public class CreditAllocationsJsonParser {
|
||||
|
||||
public final CreditAllocationsValidator creditAllocationsValidator;
|
||||
|
||||
public List<LoanProductCreditAllocationRule> assembleLoanProductCreditAllocationRules(final JsonCommand command,
|
||||
String loanTransactionProcessingStrategyCode) {
|
||||
JsonArray creditAllocation = command.arrayOfParameterNamed("creditAllocation");
|
||||
List<LoanProductCreditAllocationRule> productCreditAllocationRules = null;
|
||||
if (creditAllocation != null) {
|
||||
productCreditAllocationRules = creditAllocation.asList().stream().map(json -> {
|
||||
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
|
||||
LoanProductCreditAllocationRule creditAllocationRule = new LoanProductCreditAllocationRule();
|
||||
populateCreditAllocationRules(map, creditAllocationRule);
|
||||
populateTransactionType(map, creditAllocationRule);
|
||||
return creditAllocationRule;
|
||||
}).toList();
|
||||
}
|
||||
creditAllocationsValidator.validate(productCreditAllocationRules, loanTransactionProcessingStrategyCode);
|
||||
return productCreditAllocationRules;
|
||||
}
|
||||
|
||||
private void populateCreditAllocationRules(Map<String, JsonElement> map, LoanProductCreditAllocationRule creditAllocationRule) {
|
||||
JsonArray creditAllocationOrder = asJsonArrayOrNull(map.get("creditAllocationOrder"));
|
||||
if (creditAllocationOrder != null) {
|
||||
creditAllocationRule.setAllocationTypes(getAllocationTypes(creditAllocationOrder));
|
||||
}
|
||||
}
|
||||
|
||||
private void populateTransactionType(Map<String, JsonElement> map, LoanProductCreditAllocationRule creditAllocationRule) {
|
||||
String transactionType = asStringOrNull(map.get("transactionType"));
|
||||
if (transactionType != null) {
|
||||
creditAllocationRule.setTransactionType(Enums.getIfPresent(CreditAllocationTransactionType.class, transactionType).orNull());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private List<AllocationType> getAllocationTypes(JsonArray allocationOrder) {
|
||||
if (allocationOrder != null) {
|
||||
List<Pair<Integer, AllocationType>> parsedListWithOrder = allocationOrder.asList().stream().map(json -> {
|
||||
Map<String, JsonElement> map = json.getAsJsonObject().asMap();
|
||||
AllocationType allocationType = null;
|
||||
String creditAllocationRule = asStringOrNull(map.get("creditAllocationRule"));
|
||||
if (creditAllocationRule != null) {
|
||||
allocationType = Enums.getIfPresent(AllocationType.class, creditAllocationRule).orNull();
|
||||
}
|
||||
return Pair.of(asIntegerOrNull(map.get("order")), allocationType);
|
||||
}).sorted(Comparator.comparing(Pair::getLeft)).toList();
|
||||
creditAllocationsValidator.validatePairOfOrderAndCreditAllocationType(parsedListWithOrder);
|
||||
return parsedListWithOrder.stream().map(Pair::getRight).toList();
|
||||
} else {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
|
||||
private Integer asIntegerOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsInt();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String asStringOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private JsonArray asJsonArrayOrNull(JsonElement element) {
|
||||
if (!element.isJsonNull()) {
|
||||
return element.getAsJsonArray();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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.loanproduct.domain;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CreditAllocationsValidator {
|
||||
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||
|
||||
public void validate(List<LoanProductCreditAllocationRule> rules, String code) {
|
||||
if (isAdvancedPaymentStrategy(code)) {
|
||||
if (hasDuplicateTransactionTypes(rules)) {
|
||||
raiseValidationError("advanced-payment-strategy-with-duplicate-credit-allocation",
|
||||
"The same transaction type must be provided only once");
|
||||
}
|
||||
|
||||
if (rules != null) {
|
||||
for (LoanProductCreditAllocationRule rule : rules) {
|
||||
validateAllocationRule(rule);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (hasLoanProductCreditAllocationRule(rules)) {
|
||||
raiseValidationError("credit_allocation.must.not.be.provided.when.allocation.strategy.is.not.advanced-payment-strategy",
|
||||
"In case '" + code + "' payment strategy, creditAllocation must not be provided");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validatePairOfOrderAndCreditAllocationType(List<Pair<Integer, AllocationType>> rules) {
|
||||
if (rules.size() != 4) {
|
||||
raiseValidationError("advanced-payment-strategy.each_credit_allocation_order.must.contain.4.entries",
|
||||
"Each provided credit allocation must contain exactly 4 allocation rules, but " + rules.size() + " were provided");
|
||||
}
|
||||
|
||||
List<AllocationType> deduped = rules.stream().map(Pair::getRight).distinct().toList();
|
||||
if (deduped.size() != 4) {
|
||||
raiseValidationError("advanced-payment-strategy.must.not.have.duplicate.credit.allocation.rule",
|
||||
"The list of provided credit allocation rules must not contain any duplicates");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(IntStream.rangeClosed(1, 4).boxed().toArray(), rules.stream().map(Pair::getLeft).toArray())) {
|
||||
raiseValidationError("advanced-payment-strategy.invalid.order", "The provided orders must be between 1 and 4");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasDuplicateTransactionTypes(List<LoanProductCreditAllocationRule> rules) {
|
||||
return rules != null
|
||||
&& rules.stream().map(LoanProductCreditAllocationRule::getTransactionType).distinct().toList().size() != rules.size();
|
||||
}
|
||||
|
||||
private void validateAllocationRule(LoanProductCreditAllocationRule rule) {
|
||||
if (rule.getTransactionType() == null) {
|
||||
raiseValidationError("advanced-payment-strategy.with.not.valid.transaction.type",
|
||||
"Credit allocation was provided with a not valid transaction type");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAdvancedPaymentStrategy(String code) {
|
||||
return ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(code);
|
||||
}
|
||||
|
||||
private boolean hasLoanProductCreditAllocationRule(List<LoanProductCreditAllocationRule> rules) {
|
||||
return rules != null && rules.size() > 0;
|
||||
}
|
||||
|
||||
private void raiseValidationError(String globalisationMessageCode, String msg) {
|
||||
throw new PlatformApiDataValidationException(List.of(ApiParameterError.generalError(globalisationMessageCode, msg)));
|
||||
}
|
||||
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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.loanproduct.mapper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.data.AdvancedPaymentData;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
|
||||
@Mapper(config = MapstructMapperConfig.class)
|
||||
public interface AdvancedPaymentDataMapper {
|
||||
|
||||
List<AdvancedPaymentData> mapLoanProductPaymentAllocationRule(List<LoanProductPaymentAllocationRule> paymentAllocationRule);
|
||||
|
||||
List<AdvancedPaymentData> mapLoanPaymentAllocationRule(List<LoanPaymentAllocationRule> paymentAllocationRule);
|
||||
|
||||
@Mapping(target = "paymentAllocationOrder", source = "allocationTypes")
|
||||
AdvancedPaymentData mapLoanProductPaymentAllocationRule(LoanProductPaymentAllocationRule paymentAllocationRule);
|
||||
|
||||
@Mapping(target = "paymentAllocationOrder", source = "allocationTypes")
|
||||
AdvancedPaymentData mapLoanPaymentAllocationRule(LoanPaymentAllocationRule paymentAllocationRule);
|
||||
|
||||
default List<AdvancedPaymentData.PaymentAllocationOrder> mapAllocationTypes(List<PaymentAllocationType> allocationTypes) {
|
||||
AtomicInteger counter = new AtomicInteger(1);
|
||||
return allocationTypes.stream().map(a -> new AdvancedPaymentData.PaymentAllocationOrder(a.name(), counter.getAndIncrement()))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule;
|
||||
|
||||
public class LoanProductCreditAllocationRuleMerger {
|
||||
|
||||
public boolean updateCreditAllocationRules(LoanProduct loanProduct,
|
||||
final List<LoanProductCreditAllocationRule> newLoanProductCreditAllocationRules) {
|
||||
if (newLoanProductCreditAllocationRules == null) {
|
||||
return false;
|
||||
}
|
||||
boolean updated = false;
|
||||
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> originalItems = loanProduct.getCreditAllocationRules()
|
||||
.stream().collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
|
||||
Map<CreditAllocationTransactionType, LoanProductCreditAllocationRule> newItems = newLoanProductCreditAllocationRules.stream()
|
||||
.collect(Collectors.toMap(LoanProductCreditAllocationRule::getTransactionType, Function.identity()));
|
||||
|
||||
// elements to be deleted
|
||||
Set<CreditAllocationTransactionType> existing = new HashSet<>(originalItems.keySet());
|
||||
Set<CreditAllocationTransactionType> newSet = new HashSet<>(newItems.keySet());
|
||||
existing.removeAll(newSet);
|
||||
if (existing.size() > 0) {
|
||||
updated = true;
|
||||
existing.forEach(type -> {
|
||||
loanProduct.getCreditAllocationRules().remove(originalItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be added
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
newSet.removeAll(existing);
|
||||
if (newSet.size() > 0) {
|
||||
updated = true;
|
||||
newSet.forEach(type -> {
|
||||
loanProduct.getCreditAllocationRules().add(newItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be merged
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
existing.retainAll(newSet);
|
||||
|
||||
for (CreditAllocationTransactionType type : existing) {
|
||||
boolean result = mergeLoanProductCreditAllocationRule(originalItems.get(type), newItems.get(type));
|
||||
if (result) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean mergeLoanProductCreditAllocationRule(LoanProductCreditAllocationRule into, LoanProductCreditAllocationRule newElement) {
|
||||
boolean changed = false;
|
||||
|
||||
if (!Objects.equals(into.getAllocationTypes(), newElement.getAllocationTypes())) {
|
||||
into.setAllocationTypes(newElement.getAllocationTypes());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* 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.loanproduct.service;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
|
||||
public class LoanProductPaymentAllocationRuleMerger {
|
||||
|
||||
public boolean updateProductPaymentAllocationRules(LoanProduct loanProduct,
|
||||
final List<LoanProductPaymentAllocationRule> newLoanProductPaymentAllocationRules) {
|
||||
if (newLoanProductPaymentAllocationRules == null) {
|
||||
return false;
|
||||
}
|
||||
boolean updated = false;
|
||||
Map<PaymentAllocationTransactionType, LoanProductPaymentAllocationRule> originalItems = loanProduct.getPaymentAllocationRules()
|
||||
.stream().collect(Collectors.toMap(LoanProductPaymentAllocationRule::getTransactionType, Function.identity()));
|
||||
Map<PaymentAllocationTransactionType, LoanProductPaymentAllocationRule> newItems = newLoanProductPaymentAllocationRules.stream()
|
||||
.collect(Collectors.toMap(LoanProductPaymentAllocationRule::getTransactionType, Function.identity()));
|
||||
|
||||
// elements to be deleted
|
||||
Set<PaymentAllocationTransactionType> existing = new HashSet<>(originalItems.keySet());
|
||||
Set<PaymentAllocationTransactionType> newSet = new HashSet<>(newItems.keySet());
|
||||
existing.removeAll(newSet);
|
||||
if (existing.size() > 0) {
|
||||
updated = true;
|
||||
existing.forEach(type -> {
|
||||
loanProduct.getPaymentAllocationRules().remove(originalItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be added
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
newSet.removeAll(existing);
|
||||
if (newSet.size() > 0) {
|
||||
updated = true;
|
||||
newSet.forEach(type -> {
|
||||
loanProduct.getPaymentAllocationRules().add(newItems.get(type));
|
||||
});
|
||||
}
|
||||
|
||||
// elements to be merged
|
||||
existing = new HashSet<>(originalItems.keySet());
|
||||
newSet = new HashSet<>(newItems.keySet());
|
||||
existing.retainAll(newSet);
|
||||
|
||||
for (PaymentAllocationTransactionType type : existing) {
|
||||
boolean result = mergeLoanProductPaymentAllocationRule(originalItems.get(type), newItems.get(type));
|
||||
if (result) {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private boolean mergeLoanProductPaymentAllocationRule(LoanProductPaymentAllocationRule into,
|
||||
LoanProductPaymentAllocationRule newElement) {
|
||||
boolean changed = false;
|
||||
|
||||
if (!Objects.equals(into.getFutureInstallmentAllocationRule(), newElement.getFutureInstallmentAllocationRule())) {
|
||||
into.setFutureInstallmentAllocationRule(newElement.getFutureInstallmentAllocationRule());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!Objects.equals(into.getAllocationTypes(), newElement.getAllocationTypes())) {
|
||||
into.setAllocationTypes(newElement.getAllocationTypes());
|
||||
changed = true;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd">
|
||||
<!-- Sequence is starting from 5000 to make it easier to move existing liquibase changesets here -->
|
||||
</databaseChangeLog>
|
||||
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
|
||||
|
||||
<persistence version="2.0"
|
||||
xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||
|
||||
<!-- This file is only used for static weaving, nothing more. -->
|
||||
<!-- You can find the runtime configuration in the JPAConfig class -->
|
||||
<persistence-unit name="jpa-pu" transaction-type="RESOURCE_LOCAL">
|
||||
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
|
||||
<!-- Fineract core module -->
|
||||
<class>org.apache.fineract.accounting.glaccount.domain.GLAccount</class>
|
||||
<class>org.apache.fineract.accounting.journalentry.domain.JournalEntry</class>
|
||||
<class>org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom</class>
|
||||
<class>org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom</class>
|
||||
<class>org.apache.fineract.infrastructure.codes.domain.Code</class>
|
||||
<class>org.apache.fineract.infrastructure.codes.domain.CodeValue</class>
|
||||
<class>org.apache.fineract.infrastructure.documentmanagement.domain.Image</class>
|
||||
<class>org.apache.fineract.organisation.staff.domain.Staff</class>
|
||||
<class>org.apache.fineract.organisation.office.domain.Office</class>
|
||||
<class>org.apache.fineract.organisation.office.domain.OrganisationCurrency</class>
|
||||
<class>org.apache.fineract.organisation.monetary.domain.ApplicationCurrency</class>
|
||||
<class>org.apache.fineract.organisation.holiday.domain.Holiday</class>
|
||||
<class>org.apache.fineract.organisation.workingdays.domain.WorkingDays</class>
|
||||
<class>org.apache.fineract.portfolio.group.domain.Group</class>
|
||||
<class>org.apache.fineract.portfolio.group.domain.GroupLevel</class>
|
||||
<class>org.apache.fineract.portfolio.group.domain.StaffAssignmentHistory</class>
|
||||
<class>org.apache.fineract.portfolio.group.domain.GroupRole</class>
|
||||
<class>org.apache.fineract.portfolio.client.domain.Client</class>
|
||||
<class>org.apache.fineract.portfolio.client.domain.ClientIdentifier</class>
|
||||
<class>org.apache.fineract.portfolio.rate.domain.Rate</class>
|
||||
<class>org.apache.fineract.portfolio.fund.domain.Fund</class>
|
||||
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket</class>
|
||||
<class>org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange</class>
|
||||
<class>org.apache.fineract.portfolio.paymenttype.domain.PaymentType</class>
|
||||
<class>org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail</class>
|
||||
<class>org.apache.fineract.portfolio.tax.domain.TaxGroup</class>
|
||||
<class>org.apache.fineract.portfolio.tax.domain.TaxGroupMappings</class>
|
||||
<class>org.apache.fineract.portfolio.tax.domain.TaxComponent</class>
|
||||
<class>org.apache.fineract.portfolio.tax.domain.TaxComponentHistory</class>
|
||||
<class>org.apache.fineract.portfolio.floatingrates.domain.FloatingRate</class>
|
||||
<class>org.apache.fineract.portfolio.floatingrates.domain.FloatingRatePeriod</class>
|
||||
<class>org.apache.fineract.portfolio.calendar.domain.Calendar</class>
|
||||
<class>org.apache.fineract.portfolio.calendar.domain.CalendarHistory</class>
|
||||
<class>org.apache.fineract.portfolio.calendar.domain.CalendarInstance</class>
|
||||
<class>org.apache.fineract.useradministration.domain.AppUser</class>
|
||||
<class>org.apache.fineract.useradministration.domain.Role</class>
|
||||
<class>org.apache.fineract.useradministration.domain.Permission</class>
|
||||
<class>org.apache.fineract.useradministration.domain.AppUserClientMapping</class>
|
||||
<!-- Fineract loan module -->
|
||||
<class>org.apache.fineract.portfolio.collateral.domain.LoanCollateral</class>
|
||||
<class>org.apache.fineract.portfolio.collateralmanagement.domain.ClientCollateralManagement</class>
|
||||
<class>org.apache.fineract.portfolio.collateralmanagement.domain.CollateralManagementDomain</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.GroupLoanIndividualMonitoringAccount</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.Loan</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanCharge</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanInstallmentCharge</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalculationDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanOfficerAssignmentHistory</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanOverdueInstallmentCharge</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariations</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTopupDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheCharge</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTrancheDisbursementCharge</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProduct</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductBorrowerCycleVariations</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductConfigurableAttributes</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductFloatingRates</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductGuaranteeDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductInterestRecalculationDetails</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductVariableInstallmentConfig</class>
|
||||
<class>org.apache.fineract.portfolio.repaymentwithpostdatedchecks.domain.PostDatedChecks</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.DueType</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.AllocationType</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.AllocationTypeListConverter</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductPaymentAllocationRule</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.LoanProductCreditAllocationRule</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType</class>
|
||||
<class>org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTypeListConverter</class>
|
||||
<class>org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter</class>
|
||||
<!-- Fineract Charge module -->
|
||||
<class>org.apache.fineract.portfolio.charge.domain.Charge</class>
|
||||
<!-- Fineract Accounting module -->
|
||||
<class>org.apache.fineract.accounting.closure.domain.GLClosure</class>
|
||||
<class>org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount</class>
|
||||
<class>org.apache.fineract.accounting.glaccount.domain.GLAccount</class>
|
||||
<class>org.apache.fineract.accounting.journalentry.domain.JournalEntry</class>
|
||||
<class>org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping</class>
|
||||
<class>org.apache.fineract.accounting.rule.domain.AccountingRule</class>
|
||||
<class>org.apache.fineract.accounting.rule.domain.AccountingTagRule</class>
|
||||
<exclude-unlisted-classes>false</exclude-unlisted-classes>
|
||||
<properties>
|
||||
<property name="eclipselink.weaving" value="static" />
|
||||
</properties>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
Reference in New Issue
Block a user