FINERACT-1932: Fineract modularization Fineract progressive loan module

This commit is contained in:
Ruchi Dhamankar
2024-05-24 14:35:34 +02:00
committed by Adam Saghy
parent f59ae64222
commit 53f4864980
53 changed files with 364 additions and 2 deletions
@@ -243,9 +243,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
@Column(name = "loan_transaction_strategy_name")
private String transactionProcessingStrategyName;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after removing association
// from Loan entity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List<LoanPaymentAllocationRule> paymentAllocationRules = new ArrayList<>();
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after removing association
// from Loan entity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loan", orphanRemoval = true, fetch = FetchType.LAZY)
private List<LoanCreditAllocationRule> creditAllocationRules = new ArrayList<>();
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanTransaction classes
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanTransaction classes
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
@@ -32,6 +32,7 @@ import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDa
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanTransaction classes
@Entity
@Table(name = "m_loan_reage_parameter")
@AllArgsConstructor
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain.reaging;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanTransaction classes
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.query.Param;
@@ -1,98 +0,0 @@
/**
* 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;
}
}
@@ -0,0 +1,148 @@
/**
* 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.data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleModelPeriod;
public final class LoanScheduleModelDownPaymentPeriod implements LoanScheduleModelPeriod {
private final int periodNumber;
private final LocalDate periodDate;
private Money principalDue;
private final Money outstandingLoanBalance;
public static LoanScheduleModelDownPaymentPeriod downPayment(final int periodNumber, final LocalDate periodDate,
final Money principalDue, final Money outstandingLoanBalance) {
return new LoanScheduleModelDownPaymentPeriod(periodNumber, periodDate, principalDue, outstandingLoanBalance);
}
private LoanScheduleModelDownPaymentPeriod(int periodNumber, LocalDate periodDate, Money principalDue, Money outstandingLoanBalance) {
this.periodNumber = periodNumber;
this.periodDate = periodDate;
this.principalDue = principalDue;
this.outstandingLoanBalance = outstandingLoanBalance;
}
@Override
public LoanSchedulePeriodData toData() {
return LoanSchedulePeriodData.downPaymentOnlyPeriod(this.periodNumber, this.periodDate, this.principalDue.getAmount(),
this.outstandingLoanBalance.getAmount());
}
@Override
public boolean isRepaymentPeriod() {
return false;
}
@Override
public boolean isDownPaymentPeriod() {
return true;
}
@Override
public Integer periodNumber() {
return this.periodNumber;
}
@Override
public LocalDate periodFromDate() {
return this.periodDate;
}
@Override
public LocalDate periodDueDate() {
return this.periodDate;
}
@Override
public BigDecimal principalDue() {
BigDecimal value = BigDecimal.ZERO;
if (this.principalDue != null) {
value = this.principalDue.getAmount();
}
return value;
}
@Override
public BigDecimal interestDue() {
return BigDecimal.ZERO;
}
@Override
public BigDecimal feeChargesDue() {
return BigDecimal.ZERO;
}
@Override
public BigDecimal penaltyChargesDue() {
return BigDecimal.ZERO;
}
@Override
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
}
@Override
public boolean isRecalculatedInterestComponent() {
return false;
}
@Override
public void addPrincipalAmount(Money principalDue) {
}
@Override
public void addInterestAmount(Money interestDue) {
}
@Override
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return null;
}
@Override
public void setEMIFixedSpecificToInstallmentTrue() {
}
@Override
public boolean isEMIFixedSpecificToInstallment() {
return false;
}
@Override
public BigDecimal rescheduleInterestPortion() {
return null;
}
@Override
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
}
}
@@ -0,0 +1,532 @@
/**
* 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.data;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.RecalculationDetail;
public final class LoanScheduleParams {
// Actual period Number as per the schedule
private int periodNumber;
// Actual period Number plus interest only repayments
private int instalmentNumber;
private LocalDate periodStartDate;
private LocalDate actualRepaymentDate;
// variables for cumulative totals
private Money totalCumulativePrincipal;
private Money totalCumulativeInterest;
private Money totalFeeChargesCharged;
private Money totalPenaltyChargesCharged;
private Money totalRepaymentExpected;
private Money totalOutstandingInterestPaymentDueToGrace;
// early payments will be added here and as per the selected strategy
// action will be performed on this value
private Money reducePrincipal;
// principal changes will be added along with date(after applying rest)
// from when these amounts will effect the outstanding balance for
// interest calculation
private final Map<LocalDate, Money> principalPortionMap;
// compounding(principal) amounts will be added along with
// date(after applying compounding frequency)
// from when these amounts will effect the outstanding balance for
// interest calculation
private final Map<LocalDate, Money> latePaymentMap;
// compounding(interest/Fee) amounts will be added along with
// date(after applying compounding frequency)
// from when these amounts will effect the outstanding balance for
// interest calculation
private final Map<LocalDate, Money> compoundingMap;
private final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
private Money unCompoundedAmount;
private Money compoundedInLastInstallment;
public Money getCompoundedInLastInstallment() {
return this.compoundedInLastInstallment;
}
public void setCompoundedInLastInstallment(Money compoundedInLastInstallment) {
this.compoundedInLastInstallment = compoundedInLastInstallment;
}
// disbursement map for tranche details(will added to outstanding
// balance as per the start date)
private final Map<LocalDate, Money> disburseDetailMap;
private Money principalToBeScheduled;
private Money outstandingBalance;
// total outstanding balance as per rest for interest calculation.
private Money outstandingBalanceAsPerRest;
private final List<LoanRepaymentScheduleInstallment> installments;
private final Collection<RecalculationDetail> recalculationDetails;
private final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor;
private final LocalDate scheduleTillDate;
private final boolean partialUpdate;
private int loanTermInDays;
private final MonetaryCurrency currency;
private final boolean applyInterestRecalculation;
private LoanScheduleParams(final int periodNumber, final int instalmentNumber, int loanTermInDays, LocalDate periodStartDate,
final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal, final Money totalCumulativeInterest,
final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged, final Money totalRepaymentExpected,
Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal, final Map<LocalDate, Money> principalPortionMap,
final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate, Money> compoundingMap, final Money unCompoundedAmount,
final Map<LocalDate, Money> disburseDetailMap, Money principalToBeScheduled, final Money outstandingBalance,
final Money outstandingBalanceAsPerRest, final List<LoanRepaymentScheduleInstallment> installments,
final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean partialUpdate, final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
this.periodNumber = periodNumber;
this.instalmentNumber = instalmentNumber;
this.loanTermInDays = loanTermInDays;
this.periodStartDate = periodStartDate;
this.actualRepaymentDate = actualRepaymentDate;
this.totalCumulativePrincipal = totalCumulativePrincipal;
this.totalCumulativeInterest = totalCumulativeInterest;
this.totalFeeChargesCharged = totalFeeChargesCharged;
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
this.totalRepaymentExpected = totalRepaymentExpected;
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
this.reducePrincipal = reducePrincipal;
this.principalPortionMap = principalPortionMap;
this.latePaymentMap = latePaymentMap;
this.compoundingMap = compoundingMap;
this.unCompoundedAmount = unCompoundedAmount;
this.disburseDetailMap = disburseDetailMap;
this.principalToBeScheduled = principalToBeScheduled;
this.outstandingBalance = outstandingBalance;
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
this.installments = installments;
this.recalculationDetails = recalculationDetails;
this.loanRepaymentScheduleTransactionProcessor = loanRepaymentScheduleTransactionProcessor;
this.scheduleTillDate = scheduleTillDate;
this.partialUpdate = partialUpdate;
this.currency = currency;
this.applyInterestRecalculation = applyInterestRecalculation;
if (this.currency != null) {
this.compoundedInLastInstallment = Money.zero(this.currency);
}
}
public static LoanScheduleParams createLoanScheduleParamsForPartialUpdate(final int periodNumber, final int instalmentNumber,
int loanTermInDays, LocalDate periodStartDate, final LocalDate actualRepaymentDate, final Money totalCumulativePrincipal,
final Money totalCumulativeInterest, final Money totalFeeChargesCharged, final Money totalPenaltyChargesCharged,
final Money totalRepaymentExpected, Money totalOutstandingInterestPaymentDueToGrace, final Money reducePrincipal,
final Map<LocalDate, Money> principalPortionMap, final Map<LocalDate, Money> latePaymentMap,
final Map<LocalDate, Money> compoundingMap, Money unCompoundedAmount, final Map<LocalDate, Money> disburseDetailMap,
final Money principalToBeScheduled, final Money outstandingBalance, final Money outstandingBalanceAsPerRest,
final List<LoanRepaymentScheduleInstallment> installments, final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final MonetaryCurrency currency, final boolean applyInterestRecalculation) {
final boolean partialUpdate = true;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParamsForCompleteUpdate(final Collection<RecalculationDetail> recalculationDetails,
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate scheduleTillDate,
final boolean applyInterestRecalculation) {
final int periodNumber = 1;
final int instalmentNumber = 1;
final LocalDate periodStartDate = null;
final LocalDate actualRepaymentDate = null;
final Money totalCumulativePrincipal = null;
final Money totalCumulativeInterest = null;
final Money totalFeeChargesCharged = null;
final Money totalPenaltyChargesCharged = null;
final Money totalRepaymentExpected = null;
final Money reducePrincipal = null;
final Map<LocalDate, Money> principalPortionMap = null;
final Map<LocalDate, Money> latePaymentMap = null;
final Map<LocalDate, Money> compoundingMap = null;
final Map<LocalDate, Money> disburseDetailMap = null;
final Money principalToBeScheduled = null;
final Money outstandingBalance = null;
final Money outstandingBalanceAsPerRest = null;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final boolean partialUpdate = false;
final int loanTermInDays = 0;
final Money totalOutstandingInterestPaymentDueToGrace = null;
final MonetaryCurrency currency = null;
final Money unCompoundedAmount = null;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled) {
final int loanTermInDays = 0;
final int periodNumber = 1;
final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
final Money outstandingBalance = principalToBeScheduled;
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final boolean partialUpdate = false;
final Collection<RecalculationDetail> recalculationDetails = null;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
final LocalDate scheduleTillDate = null;
final Money unCompoundedAmount = Money.zero(currency);
final boolean applyInterestRecalculation = false;
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
}
public static LoanScheduleParams createLoanScheduleParams(final MonetaryCurrency currency, final Money chargesDueAtTimeOfDisbursement,
final LocalDate periodStartDate, final Money principalToBeScheduled, final LoanScheduleParams loanScheduleParams) {
final int loanTermInDays = 0;
final int periodNumber = 1;
final int instalmentNumber = 1;
final Money totalCumulativePrincipal = Money.zero(currency);
final Money totalCumulativeInterest = Money.zero(currency);
final Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
final LocalDate actualRepaymentDate = periodStartDate;
final Money totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
final Money totalPenaltyChargesCharged = Money.zero(currency);
final Money totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
final Money reducePrincipal = Money.zero(currency);
final Map<LocalDate, Money> principalPortionMap = new HashMap<>();
final Map<LocalDate, Money> latePaymentMap = new HashMap<>();
final Map<LocalDate, Money> compoundingMap = new TreeMap<>();
final Map<LocalDate, Money> disburseDetailMap = new TreeMap<>();
final Money outstandingBalance = principalToBeScheduled;
final Money outstandingBalanceAsPerRest = principalToBeScheduled;
final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
final boolean partialUpdate = false;
final Collection<RecalculationDetail> recalculationDetails = loanScheduleParams.recalculationDetails;
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = loanScheduleParams.loanRepaymentScheduleTransactionProcessor;
final LocalDate scheduleTillDate = loanScheduleParams.scheduleTillDate;
final boolean applyInterestRecalculation = loanScheduleParams.applyInterestRecalculation;
final Money unCompoundedAmount = Money.zero(currency);
return new LoanScheduleParams(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate,
totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged,
totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap,
compoundingMap, unCompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance,
outstandingBalanceAsPerRest, installments, recalculationDetails, loanRepaymentScheduleTransactionProcessor,
scheduleTillDate, partialUpdate, currency, applyInterestRecalculation);
}
public int getPeriodNumber() {
return this.periodNumber;
}
public void setPeriodNumber(int periodNumber) {
this.periodNumber = periodNumber;
}
public int getInstalmentNumber() {
return this.instalmentNumber;
}
public LocalDate getActualRepaymentDate() {
return this.actualRepaymentDate;
}
public void setActualRepaymentDate(LocalDate actualRepaymentDate) {
this.actualRepaymentDate = actualRepaymentDate;
}
public Money getTotalCumulativePrincipal() {
return this.totalCumulativePrincipal;
}
public void addTotalCumulativePrincipal(final Money totalCumulativePrincipal) {
if (this.totalCumulativePrincipal != null) {
this.totalCumulativePrincipal = this.totalCumulativePrincipal.plus(totalCumulativePrincipal);
} else {
this.totalCumulativePrincipal = totalCumulativePrincipal;
}
}
public Money getTotalCumulativeInterest() {
return this.totalCumulativeInterest;
}
public void addTotalCumulativeInterest(final Money totalCumulativeInterest) {
if (this.totalCumulativeInterest != null) {
this.totalCumulativeInterest = this.totalCumulativeInterest.plus(totalCumulativeInterest);
} else {
this.totalCumulativeInterest = totalCumulativeInterest;
}
}
public Money getTotalFeeChargesCharged() {
return this.totalFeeChargesCharged;
}
public void addTotalFeeChargesCharged(final Money totalFeeChargesCharged) {
if (this.totalFeeChargesCharged != null) {
this.totalFeeChargesCharged = this.totalFeeChargesCharged.plus(totalFeeChargesCharged);
} else {
this.totalFeeChargesCharged = totalFeeChargesCharged;
}
}
public Money getTotalPenaltyChargesCharged() {
return this.totalPenaltyChargesCharged;
}
public void addTotalPenaltyChargesCharged(final Money totalPenaltyChargesCharged) {
if (this.totalPenaltyChargesCharged != null) {
this.totalPenaltyChargesCharged = this.totalPenaltyChargesCharged.plus(totalPenaltyChargesCharged);
} else {
this.totalPenaltyChargesCharged = totalPenaltyChargesCharged;
}
}
public Money getTotalRepaymentExpected() {
return this.totalRepaymentExpected;
}
public void addTotalRepaymentExpected(final Money totalRepaymentExpected) {
if (this.totalRepaymentExpected != null) {
this.totalRepaymentExpected = this.totalRepaymentExpected.plus(totalRepaymentExpected);
} else {
this.totalRepaymentExpected = totalRepaymentExpected;
}
}
public Money getReducePrincipal() {
return this.reducePrincipal;
}
public void setReducePrincipal(Money reducePrincipal) {
this.reducePrincipal = reducePrincipal;
}
public Map<LocalDate, Money> getPrincipalPortionMap() {
return this.principalPortionMap;
}
public Map<LocalDate, Money> getLatePaymentMap() {
return this.latePaymentMap;
}
public Map<LocalDate, Money> getCompoundingMap() {
return this.compoundingMap;
}
public Map<LocalDate, Money> getDisburseDetailMap() {
return this.disburseDetailMap;
}
public Money getOutstandingBalance() {
return this.outstandingBalance;
}
public void setOutstandingBalance(Money outstandingBalance) {
this.outstandingBalance = outstandingBalance;
}
public Money getOutstandingBalanceAsPerRest() {
return this.outstandingBalanceAsPerRest;
}
public void setOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
}
public List<LoanRepaymentScheduleInstallment> getInstallments() {
return this.installments;
}
public Collection<RecalculationDetail> getRecalculationDetails() {
return this.recalculationDetails;
}
public LoanRepaymentScheduleTransactionProcessor getLoanRepaymentScheduleTransactionProcessor() {
return this.loanRepaymentScheduleTransactionProcessor;
}
public LocalDate getScheduleTillDate() {
return this.scheduleTillDate;
}
public boolean isPartialUpdate() {
return this.partialUpdate;
}
public LocalDate getPeriodStartDate() {
return this.periodStartDate;
}
public void setPeriodStartDate(LocalDate periodStartDate) {
this.periodStartDate = periodStartDate;
}
public Money getPrincipalToBeScheduled() {
return this.principalToBeScheduled;
}
public void setPrincipalToBeScheduled(Money principalToBeScheduled) {
this.principalToBeScheduled = principalToBeScheduled;
}
public void incrementPeriodNumber() {
this.periodNumber++;
}
public void incrementInstalmentNumber() {
this.instalmentNumber++;
}
public void addReducePrincipal(Money reducePrincipal) {
if (this.reducePrincipal != null) {
this.reducePrincipal = this.reducePrincipal.plus(reducePrincipal);
} else {
this.reducePrincipal = reducePrincipal;
}
}
public void reduceReducePrincipal(Money reducePrincipal) {
if (this.reducePrincipal != null) {
this.reducePrincipal = this.reducePrincipal.minus(reducePrincipal);
}
}
public void addPrincipalToBeScheduled(Money principalToBeScheduled) {
if (this.principalToBeScheduled != null) {
this.principalToBeScheduled = this.principalToBeScheduled.plus(principalToBeScheduled);
} else {
this.principalToBeScheduled = principalToBeScheduled;
}
}
public void addOutstandingBalance(Money outstandingBalance) {
if (this.outstandingBalance != null) {
this.outstandingBalance = this.outstandingBalance.plus(outstandingBalance);
} else {
this.outstandingBalance = outstandingBalance;
}
}
public void reduceOutstandingBalance(Money outstandingBalance) {
if (this.outstandingBalance != null) {
this.outstandingBalance = this.outstandingBalance.minus(outstandingBalance);
}
}
public void addOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
if (this.outstandingBalanceAsPerRest != null) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.plus(outstandingBalanceAsPerRest);
} else {
this.outstandingBalanceAsPerRest = outstandingBalanceAsPerRest;
}
}
public void reduceOutstandingBalanceAsPerRest(Money outstandingBalanceAsPerRest) {
if (this.outstandingBalanceAsPerRest != null) {
this.outstandingBalanceAsPerRest = this.outstandingBalanceAsPerRest.minus(outstandingBalanceAsPerRest);
}
}
public int getLoanTermInDays() {
return this.loanTermInDays;
}
public void addLoanTermInDays(int loanTermInDays) {
this.loanTermInDays += loanTermInDays;
}
public Money getTotalOutstandingInterestPaymentDueToGrace() {
return this.totalOutstandingInterestPaymentDueToGrace;
}
public void setTotalOutstandingInterestPaymentDueToGrace(Money totalOutstandingInterestPaymentDueToGrace) {
this.totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
}
public Map<LocalDate, Map<LocalDate, Money>> getCompoundingDateVariations() {
return this.compoundingDateVariations;
}
public MonetaryCurrency getCurrency() {
return this.currency;
}
public boolean applyInterestRecalculation() {
return this.applyInterestRecalculation;
}
public Money getUnCompoundedAmount() {
return this.unCompoundedAmount;
}
public void setUnCompoundedAmount(Money unCompoundedAmount) {
this.unCompoundedAmount = unCompoundedAmount;
}
public void addUnCompoundedAmount(Money unCompoundedAmount) {
if (this.unCompoundedAmount != null) {
this.unCompoundedAmount = this.unCompoundedAmount.plus(unCompoundedAmount);
} else {
this.unCompoundedAmount = unCompoundedAmount;
}
}
public void minusUnCompoundedAmount(Money unCompoundedAmount) {
if (this.unCompoundedAmount != null) {
this.unCompoundedAmount = this.unCompoundedAmount.minus(unCompoundedAmount);
}
}
public boolean isFirstPeriod() {
return 1 == instalmentNumber;
}
}
@@ -0,0 +1,155 @@
/**
* 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.time.LocalDate;
import java.util.Set;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
/**
* Domain representation of a Loan Schedule Disbursement Period (not used for persistence)
*/
public final class LoanScheduleModelDisbursementPeriod implements LoanScheduleModelPeriod {
@SuppressWarnings("unused")
private final Integer periodNumber;
private final LocalDate disbursementDate;
private final Money principalDisbursed;
private final BigDecimal chargesDueAtTimeOfDisbursement;
private boolean isEMIFixedSpecificToInstallment = false;
public static LoanScheduleModelDisbursementPeriod disbursement(final LoanApplicationTerms loanApplicationTerms,
final BigDecimal chargesDueAtTimeOfDisbursement) {
return new LoanScheduleModelDisbursementPeriod(null, loanApplicationTerms.getExpectedDisbursementDate(),
loanApplicationTerms.getPrincipal(), chargesDueAtTimeOfDisbursement);
}
public static LoanScheduleModelDisbursementPeriod disbursement(final LocalDate disbursementDate, final Money principalDisbursed,
final BigDecimal chargesDueAtTimeOfDisbursement) {
return new LoanScheduleModelDisbursementPeriod(null, disbursementDate, principalDisbursed, chargesDueAtTimeOfDisbursement);
}
private LoanScheduleModelDisbursementPeriod(final Integer periodNumber, final LocalDate disbursementDate,
final Money principalDisbursed, final BigDecimal chargesDueAtTimeOfDisbursement) {
this.periodNumber = periodNumber;
this.disbursementDate = disbursementDate;
this.principalDisbursed = principalDisbursed;
this.chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement;
}
@Override
public LoanSchedulePeriodData toData() {
return LoanSchedulePeriodData.disbursementOnlyPeriod(this.disbursementDate, this.principalDisbursed.getAmount(),
this.chargesDueAtTimeOfDisbursement, false);
}
@Override
public boolean isRepaymentPeriod() {
return false;
}
@Override
public boolean isDownPaymentPeriod() {
return false;
}
@Override
public Integer periodNumber() {
return null;
}
@Override
public LocalDate periodFromDate() {
return null;
}
@Override
public LocalDate periodDueDate() {
return null;
}
@Override
public BigDecimal principalDue() {
return null;
}
@Override
public BigDecimal interestDue() {
return null;
}
@Override
public BigDecimal feeChargesDue() {
return null;
}
@Override
public BigDecimal penaltyChargesDue() {
return null;
}
@Override
public void addLoanCharges(@SuppressWarnings("unused") BigDecimal feeCharge, @SuppressWarnings("unused") BigDecimal penaltyCharge) {
return;
}
@Override
public boolean isRecalculatedInterestComponent() {
return false;
}
@Override
public void addPrincipalAmount(@SuppressWarnings("unused") Money principalDue) {
return;
}
@Override
public void addInterestAmount(@SuppressWarnings("unused") Money principalDue) {
return;
}
@Override
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return null;
}
@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}
@Override
public boolean isEMIFixedSpecificToInstallment() {
return isEMIFixedSpecificToInstallment;
}
@Override
public BigDecimal rescheduleInterestPortion() {
return null;
}
@Override
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
return;
}
}
@@ -0,0 +1,193 @@
/**
* 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.time.LocalDate;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
import org.apache.fineract.organisation.monetary.domain.Money;
import org.apache.fineract.portfolio.loanaccount.domain.LoanInterestRecalcualtionAdditionalDetails;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePeriodData;
/**
* Domain representation of a Loan Schedule Repayment Period (not used for persistence)
*/
@Getter
public final class LoanScheduleModelRepaymentPeriod implements LoanScheduleModelPeriod {
private final int periodNumber;
private final LocalDate fromDate;
private final LocalDate dueDate;
private Money principalDue;
private final Money outstandingLoanBalance;
private Money interestDue;
private Money feeChargesDue;
private Money penaltyChargesDue;
private Money totalDue;
private final boolean recalculatedInterestComponent;
private final Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
private boolean isEMIFixedSpecificToInstallment = false;
BigDecimal rescheduleInterestPortion;
public static LoanScheduleModelRepaymentPeriod repayment(final int periodNumber, final LocalDate startDate,
final LocalDate scheduledDueDate, final Money principalDue, final Money outstandingLoanBalance, final Money interestDue,
final Money feeChargesDue, final Money penaltyChargesDue, final Money totalDue, boolean recalculatedInterestComponent) {
return new LoanScheduleModelRepaymentPeriod(periodNumber, startDate, scheduledDueDate, principalDue, outstandingLoanBalance,
interestDue, feeChargesDue, penaltyChargesDue, totalDue, recalculatedInterestComponent);
}
public LoanScheduleModelRepaymentPeriod(final int periodNumber, final LocalDate fromDate, final LocalDate dueDate,
final Money principalDue, final Money outstandingLoanBalance, final Money interestDue, final Money feeChargesDue,
final Money penaltyChargesDue, final Money totalDue, final boolean recalculatedInterestComponent) {
this.periodNumber = periodNumber;
this.fromDate = fromDate;
this.dueDate = dueDate;
this.principalDue = principalDue;
this.outstandingLoanBalance = outstandingLoanBalance;
this.interestDue = interestDue;
this.feeChargesDue = feeChargesDue;
this.penaltyChargesDue = penaltyChargesDue;
this.totalDue = totalDue;
this.recalculatedInterestComponent = recalculatedInterestComponent;
}
@Override
public LoanSchedulePeriodData toData() {
return LoanSchedulePeriodData.repaymentOnlyPeriod(this.periodNumber, this.fromDate, this.dueDate, this.principalDue.getAmount(),
this.outstandingLoanBalance.getAmount(), this.interestDue.getAmount(), this.feeChargesDue.getAmount(),
this.penaltyChargesDue.getAmount(), this.totalDue.getAmount(), this.principalDue.plus(this.interestDue).getAmount());
}
@Override
public boolean isRepaymentPeriod() {
return true;
}
@Override
public boolean isDownPaymentPeriod() {
return false;
}
@Override
public Integer periodNumber() {
return this.periodNumber;
}
@Override
public LocalDate periodFromDate() {
return this.fromDate;
}
@Override
public LocalDate periodDueDate() {
return this.dueDate;
}
@Override
public BigDecimal principalDue() {
BigDecimal value = null;
if (this.principalDue != null) {
value = this.principalDue.getAmount();
}
return value;
}
@Override
public BigDecimal interestDue() {
BigDecimal value = null;
if (this.interestDue != null) {
value = this.interestDue.getAmount();
}
return value;
}
@Override
public BigDecimal feeChargesDue() {
BigDecimal value = null;
if (this.feeChargesDue != null) {
value = this.feeChargesDue.getAmount();
}
return value;
}
@Override
public BigDecimal penaltyChargesDue() {
BigDecimal value = null;
if (this.penaltyChargesDue != null) {
value = this.penaltyChargesDue.getAmount();
}
return value;
}
@Override
public void addLoanCharges(BigDecimal feeCharge, BigDecimal penaltyCharge) {
this.feeChargesDue = this.feeChargesDue.plus(feeCharge);
this.penaltyChargesDue = this.penaltyChargesDue.plus(penaltyCharge);
this.totalDue = this.totalDue.plus(feeCharge).plus(penaltyCharge);
}
@Override
public void addPrincipalAmount(final Money principalDue) {
this.principalDue = this.principalDue.plus(principalDue);
this.totalDue = this.totalDue.plus(principalDue);
}
@Override
public boolean isRecalculatedInterestComponent() {
return this.recalculatedInterestComponent;
}
@Override
public void addInterestAmount(Money interestDue) {
this.interestDue = this.interestDue.plus(interestDue);
this.totalDue = this.totalDue.plus(interestDue);
}
@Override
public Set<LoanInterestRecalcualtionAdditionalDetails> getLoanCompoundingDetails() {
return this.loanCompoundingDetails;
}
@Override
public boolean isEMIFixedSpecificToInstallment() {
return this.isEMIFixedSpecificToInstallment;
}
@Override
public void setEMIFixedSpecificToInstallmentTrue() {
this.isEMIFixedSpecificToInstallment = true;
}
@Override
public void setRescheduleInterestPortion(BigDecimal rescheduleInterestPortion) {
this.rescheduleInterestPortion = rescheduleInterestPortion;
}
@Override
public BigDecimal rescheduleInterestPortion() {
return this.rescheduleInterestPortion;
}
}
@@ -0,0 +1,27 @@
/**
* 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.time.LocalDate;
import java.util.List;
import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData;
public record LoanTermVariationParams(boolean skipPeriod, boolean recalculateAmounts, LocalDate scheduledDueDate,
List<LoanTermVariationsData> variationsData) {
}
@@ -0,0 +1,50 @@
/**
* 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.time.LocalDate;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
public class RecalculationDetail {
private LocalDate transactionDate;
private boolean isProcessed;
private LoanTransaction transaction;
public RecalculationDetail(final LocalDate transactionDate, final LoanTransaction transaction) {
this.transactionDate = transactionDate;
this.transaction = transaction;
}
public LocalDate getTransactionDate() {
return this.transactionDate;
}
public LoanTransaction getTransaction() {
return this.transaction;
}
public boolean isProcessed() {
return this.isProcessed;
}
public void setProcessed(boolean isProcessed) {
this.isProcessed = isProcessed;
}
}
@@ -0,0 +1,160 @@
/**
* 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 org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
import org.apache.fineract.organisation.monetary.domain.Money;
public class ScheduleCurrentPeriodParams {
Money earlyPaidAmount;
LoanScheduleModelPeriod lastInstallment;
boolean skipCurrentLoop;
Money interestForThisPeriod;
Money principalForThisPeriod;
Money feeChargesForInstallment;
Money penaltyChargesForInstallment;
// for adjusting outstandingBalances
Money reducedBalance;
boolean isEmiAmountChanged;
BigDecimal interestCalculationGraceOnRepaymentPeriodFraction;
ScheduleCurrentPeriodParams(final MonetaryCurrency currency) {
this(currency, BigDecimal.ZERO);
}
ScheduleCurrentPeriodParams(final MonetaryCurrency currency, BigDecimal interestCalculationGraceOnRepaymentPeriodFraction) {
this.earlyPaidAmount = Money.zero(currency);
this.lastInstallment = null;
this.skipCurrentLoop = false;
this.interestForThisPeriod = Money.zero(currency);
this.principalForThisPeriod = Money.zero(currency);
this.reducedBalance = Money.zero(currency);
this.feeChargesForInstallment = Money.zero(currency);
this.penaltyChargesForInstallment = Money.zero(currency);
this.isEmiAmountChanged = false;
this.interestCalculationGraceOnRepaymentPeriodFraction = interestCalculationGraceOnRepaymentPeriodFraction;
}
public Money getEarlyPaidAmount() {
return this.earlyPaidAmount;
}
public void plusEarlyPaidAmount(Money earlyPaidAmount) {
this.earlyPaidAmount = this.earlyPaidAmount.plus(earlyPaidAmount);
}
public void minusEarlyPaidAmount(Money earlyPaidAmount) {
this.earlyPaidAmount = this.earlyPaidAmount.minus(earlyPaidAmount);
}
public LoanScheduleModelPeriod getLastInstallment() {
return this.lastInstallment;
}
public void setLastInstallment(LoanScheduleModelPeriod lastInstallment) {
this.lastInstallment = lastInstallment;
}
public boolean isSkipCurrentLoop() {
return this.skipCurrentLoop;
}
public void setSkipCurrentLoop(boolean skipCurrentLoop) {
this.skipCurrentLoop = skipCurrentLoop;
}
public Money getInterestForThisPeriod() {
return this.interestForThisPeriod;
}
public void setInterestForThisPeriod(Money interestForThisPeriod) {
this.interestForThisPeriod = interestForThisPeriod;
}
public void minusInterestForThisPeriod(Money interestForThisPeriod) {
this.interestForThisPeriod = this.interestForThisPeriod.minus(interestForThisPeriod);
}
public Money getPrincipalForThisPeriod() {
return this.principalForThisPeriod;
}
public void setPrincipalForThisPeriod(Money principalForThisPeriod) {
this.principalForThisPeriod = principalForThisPeriod;
}
public void plusPrincipalForThisPeriod(Money principalForThisPeriod) {
this.principalForThisPeriod = this.principalForThisPeriod.plus(principalForThisPeriod);
}
public void minusPrincipalForThisPeriod(Money principalForThisPeriod) {
this.principalForThisPeriod = this.principalForThisPeriod.minus(principalForThisPeriod);
}
public Money getReducedBalance() {
return this.reducedBalance;
}
public void setReducedBalance(Money reducedBalance) {
this.reducedBalance = reducedBalance;
}
public Money getFeeChargesForInstallment() {
return this.feeChargesForInstallment;
}
public void setFeeChargesForInstallment(Money feeChargesForInstallment) {
this.feeChargesForInstallment = feeChargesForInstallment;
}
public void minusFeeChargesForInstallment(Money feeChargesForInstallment) {
this.feeChargesForInstallment = this.feeChargesForInstallment.minus(feeChargesForInstallment);
}
public Money getPenaltyChargesForInstallment() {
return this.penaltyChargesForInstallment;
}
public void setPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
this.penaltyChargesForInstallment = penaltyChargesForInstallment;
}
public void minusPenaltyChargesForInstallment(Money penaltyChargesForInstallment) {
this.penaltyChargesForInstallment = this.penaltyChargesForInstallment.minus(penaltyChargesForInstallment);
}
public Money fetchTotalAmountForPeriod() {
return this.principalForThisPeriod.plus(interestForThisPeriod).plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);
}
public boolean isEmiAmountChanged() {
return this.isEmiAmountChanged;
}
public void setEmiAmountChanged(boolean isEmiAmountChanged) {
this.isEmiAmountChanged = isEmiAmountChanged;
}
public BigDecimal getInterestCalculationGraceOnRepaymentPeriodFraction() {
return this.interestCalculationGraceOnRepaymentPeriodFraction;
}
}
@@ -0,0 +1,52 @@
/**
* 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.time.LocalDate;
import org.apache.fineract.organisation.workingdays.data.AdjustedDateDetailsDTO;
import org.apache.fineract.portfolio.calendar.domain.Calendar;
import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
public interface ScheduledDateGenerator {
LocalDate getLastRepaymentDate(LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
LocalDate idealDisbursementDateBasedOnFirstRepaymentDate(PeriodFrequencyType repaymentPeriodFrequencyType, int repaidEvery,
LocalDate firstRepaymentDate, Calendar loanCalendar, HolidayDetailDTO holidayDetailDTO,
LoanApplicationTerms loanApplicationTerms);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment);
LocalDate generateNextRepaymentDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms, boolean isFirstRepayment,
Integer periodNumber);
AdjustedDateDetailsDTO adjustRepaymentDate(LocalDate dueRepaymentPeriodDate, LoanApplicationTerms loanApplicationTerms,
HolidayDetailDTO holidayDetailDTO);
LocalDate getRepaymentPeriodDate(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate);
Boolean isDateFallsInSchedule(PeriodFrequencyType frequency, int repaidEvery, LocalDate startDate, LocalDate date);
LocalDate generateNextScheduleDateStartingFromDisburseDate(LocalDate lastRepaymentDate, LoanApplicationTerms loanApplicationTerms,
HolidayDetailDTO holidayDetailDTO);
LocalDate generateNextScheduleDateStartingFromDisburseDateOrRescheduleDate(LocalDate lastRepaymentDate,
LoanApplicationTerms loanApplicationTerms, HolidayDetailDTO holidayDetailDTO);
}
@@ -0,0 +1,29 @@
/**
* 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.exception;
import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
public class MultiDisbursementOutstandingAmoutException extends AbstractPlatformDomainRuleException {
public MultiDisbursementOutstandingAmoutException(final String defaultUserMessage, final Object... defaultUserMessageArgs) {
super("error.msg.loanschedule.exceeding.max.outstanding.balance", defaultUserMessage, defaultUserMessageArgs);
}
}
@@ -1,122 +0,0 @@
/**
* 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;
}
}
@@ -1,139 +0,0 @@
/**
* 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)));
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
@@ -1,110 +0,0 @@
/**
* 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;
}
}
@@ -1,96 +0,0 @@
/**
* 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)));
}
}
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
@@ -96,9 +96,13 @@ public class LoanProduct extends AbstractPersistableCustom {
@Column(name = "loan_transaction_strategy_name")
private String transactionProcessingStrategyName;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after removing association
// from LoanProduct entity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanProduct", orphanRemoval = true, fetch = FetchType.EAGER)
private List<LoanProductPaymentAllocationRule> paymentAllocationRules = new ArrayList<>();
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after removing association
// from LoanProduct entity
@OneToMany(cascade = CascadeType.ALL, mappedBy = "loanProduct", orphanRemoval = true, fetch = FetchType.EAGER)
private List<LoanProductCreditAllocationRule> creditAllocationRules = new ArrayList<>();
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import java.util.Arrays;
import java.util.List;
import lombok.Getter;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
@@ -18,6 +18,7 @@
*/
package org.apache.fineract.portfolio.loanproduct.domain;
// TODO FINERACT-1932-Fineract modularization: Move to fineract-progressive-loan module after refactor of Loan and LoanProduct classes
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import java.util.List;