FINERACT-2059: Re-aging repayment schedule handling
This commit is contained in:
+2
-1
@@ -24,7 +24,8 @@ public interface LoanReAgingApiConstants {
|
||||
String dateFormatParameterName = "dateFormat";
|
||||
String externalIdParameterName = "externalId";
|
||||
|
||||
String frequency = "frequency";
|
||||
String frequencyType = "frequencyType";
|
||||
String frequencyNumber = "frequencyNumber";
|
||||
String startDate = "startDate";
|
||||
String numberOfInstallments = "numberOfInstallments";
|
||||
}
|
||||
|
||||
+8
-5
@@ -2166,8 +2166,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private LocalDate determineExpectedMaturityDate() {
|
||||
final int numberOfInstallments = this.repaymentScheduleInstallments.size();
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.isDownPayment() && !i.isAdditional()).toList();
|
||||
final int numberOfInstallments = installments.size();
|
||||
LocalDate maturityDate = installments.get(numberOfInstallments - 1).getDueDate();
|
||||
ListIterator<LoanRepaymentScheduleInstallment> iterator = installments.listIterator(numberOfInstallments);
|
||||
while (iterator.hasPrevious()) {
|
||||
@@ -3432,7 +3433,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||
List<LoanTransaction> trans = getLoanTransactions();
|
||||
for (final LoanTransaction transaction : trans) {
|
||||
if (transaction.isNotReversed() && (transaction.isChargeOff() || !transaction.isNonMonetaryTransaction())) {
|
||||
if (transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge() || transaction.isReAmortize()
|
||||
|| !transaction.isNonMonetaryTransaction())) {
|
||||
repaymentsOrWaivers.add(transaction);
|
||||
}
|
||||
}
|
||||
@@ -3670,10 +3672,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
}
|
||||
|
||||
private LocalDate getNextUnpaidInstallmentDueDate() {
|
||||
LocalDate nextUnpaidInstallmentDate = null;
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
LocalDate currentBusinessDate = DateUtils.getBusinessLocalDate();
|
||||
LocalDate expectedMaturityDate = determineExpectedMaturityDate();
|
||||
LocalDate nextUnpaidInstallmentDate = expectedMaturityDate;
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment installment : installments) {
|
||||
boolean isCurrentDateBeforeInstallmentAndLoanPeriod = DateUtils.isBefore(currentBusinessDate, installment.getDueDate())
|
||||
@@ -5664,7 +5666,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
lastCompoundingDate = compoundingDetail.getEffectiveDate();
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
|
||||
LoanRepaymentScheduleInstallment lastInstallment = LoanRepaymentScheduleInstallment
|
||||
.getLastNonDownPaymentInstallment(installments);
|
||||
reverseTransactionsPostEffectiveDate(incomeTransactions, lastInstallment.getDueDate());
|
||||
reverseTransactionsPostEffectiveDate(accrualTransactions, lastInstallment.getDueDate());
|
||||
}
|
||||
|
||||
+42
@@ -29,6 +29,7 @@ import jakarta.persistence.Table;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
@@ -145,6 +146,9 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
@Column(name = "is_down_payment", nullable = false)
|
||||
private boolean isDownPayment;
|
||||
|
||||
@Column(name = "is_re_aged", nullable = false)
|
||||
private boolean isReAged;
|
||||
|
||||
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "loanRepaymentScheduleInstallment")
|
||||
private Set<LoanInterestRecalcualtionAdditionalDetails> loanCompoundingDetails = new HashSet<>();
|
||||
|
||||
@@ -223,6 +227,36 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
this.obligationsMet = false;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment(Loan loan, Integer installmentNumber, LocalDate fromDate, LocalDate dueDate,
|
||||
BigDecimal principal, BigDecimal interestCharged, BigDecimal feeChargesCharged, BigDecimal penaltyCharges,
|
||||
BigDecimal creditedPrincipal, BigDecimal creditedFee, BigDecimal creditedPenalty, boolean additional, boolean isDownPayment,
|
||||
boolean isReAged) {
|
||||
this.loan = loan;
|
||||
this.installmentNumber = installmentNumber;
|
||||
this.fromDate = fromDate;
|
||||
this.dueDate = dueDate;
|
||||
this.principal = principal;
|
||||
this.interestCharged = interestCharged;
|
||||
this.feeChargesCharged = feeChargesCharged;
|
||||
this.penaltyCharges = penaltyCharges;
|
||||
this.creditedPrincipal = creditedPrincipal;
|
||||
this.creditedFee = creditedFee;
|
||||
this.creditedPenalty = creditedPenalty;
|
||||
this.additional = additional;
|
||||
this.isDownPayment = isDownPayment;
|
||||
this.isReAged = isReAged;
|
||||
}
|
||||
|
||||
public static LoanRepaymentScheduleInstallment newReAgedInstallment(final Loan loan, final Integer installmentNumber,
|
||||
final LocalDate fromDate, final LocalDate dueDate, final BigDecimal principal) {
|
||||
return new LoanRepaymentScheduleInstallment(loan, installmentNumber, fromDate, dueDate, principal, null, null, null, null, null,
|
||||
null, false, false, true);
|
||||
}
|
||||
|
||||
public static LoanRepaymentScheduleInstallment getLastNonDownPaymentInstallment(List<LoanRepaymentScheduleInstallment> installments) {
|
||||
return installments.stream().filter(i -> !i.isDownPayment()).reduce((first, second) -> second).orElseThrow();
|
||||
}
|
||||
|
||||
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
|
||||
BigDecimal result = value;
|
||||
if (BigDecimal.ZERO.compareTo(value) == 0) {
|
||||
@@ -400,6 +434,10 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
return this.installmentNumber.compareTo(o.installmentNumber);
|
||||
}
|
||||
|
||||
public int compareToByDueDate(LoanRepaymentScheduleInstallment o) {
|
||||
return this.dueDate.compareTo(o.dueDate);
|
||||
}
|
||||
|
||||
public boolean isPrincipalNotCompleted(final MonetaryCurrency currency) {
|
||||
return !isPrincipalCompleted(currency);
|
||||
}
|
||||
@@ -1022,4 +1060,8 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
public enum PaymentAction {
|
||||
PAY, UNPAY
|
||||
}
|
||||
|
||||
public boolean isReAged() {
|
||||
return isReAged;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -865,7 +865,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|
||||
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|
||||
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf())
|
||||
|| LoanTransactionType.CHARGE_OFF.equals(getTypeOf()));
|
||||
|| LoanTransactionType.CHARGE_OFF.equals(getTypeOf()) || LoanTransactionType.REAMORTIZE.equals(getTypeOf())
|
||||
|| LoanTransactionType.REAGE.equals(getTypeOf()));
|
||||
}
|
||||
|
||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||
|
||||
+3
-1
@@ -61,7 +61,9 @@ public enum LoanTransactionType {
|
||||
CHARGE_ADJUSTMENT(26, "loanTransactionType.chargeAdjustment"), //
|
||||
CHARGE_OFF(27, "loanTransactionType.chargeOff"), //
|
||||
DOWN_PAYMENT(28, "loanTransactionType.downPayment"), //
|
||||
REAGE(29, "loanTransactionType.reAge"), REAMORTIZE(30, "loanTransactionType.reAmortize");
|
||||
REAGE(29, "loanTransactionType.reAge"), //
|
||||
REAMORTIZE(30, "loanTransactionType.reAmortize"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
private final String code;
|
||||
|
||||
+5
-2
@@ -40,8 +40,11 @@ public class LoanReAgeParameter extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
private Long loanTransactionId;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
@Column(name = "frequency", nullable = false)
|
||||
private PeriodFrequencyType frequency;
|
||||
@Column(name = "frequency_type", nullable = false)
|
||||
private PeriodFrequencyType frequencyType;
|
||||
|
||||
@Column(name = "frequency_number", nullable = false)
|
||||
private Integer frequencyNumber;
|
||||
|
||||
@Column(name = "start_date", nullable = false)
|
||||
private LocalDate startDate;
|
||||
|
||||
+6
-5
@@ -479,7 +479,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
installments.sort(byDate);
|
||||
List<LoanRepaymentScheduleInstallment> installmentToBeProcessed = installments.stream().filter(i -> !i.isDownPayment())
|
||||
.sorted(byDate).toList();
|
||||
final Money zeroMoney = Money.zero(currency);
|
||||
Money transactionAmount = loanTransaction.getAmount(currency);
|
||||
Money principalPortion = MathUtil.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
|
||||
@@ -492,7 +493,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
boolean loanTransactionMapped = false;
|
||||
LocalDate pastDueDate = null;
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installmentToBeProcessed) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
@@ -526,7 +527,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installmentToBeProcessed.get(installmentToBeProcessed.size() - 1);
|
||||
currentInstallment.addToCreditedPrincipal(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, transactionAmount);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
@@ -848,7 +849,8 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
protected void addChargeOnlyRepaymentInstallmentIfRequired(Set<LoanCharge> charges,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
if (!CollectionUtils.isEmpty(charges) && !CollectionUtils.isEmpty(installments)) {
|
||||
LoanRepaymentScheduleInstallment latestRepaymentScheduleInstalment = installments.get(installments.size() - 1);
|
||||
LoanRepaymentScheduleInstallment latestRepaymentScheduleInstalment = installments.stream().filter(i -> !i.isDownPayment())
|
||||
.reduce((first, second) -> second).orElseThrow();
|
||||
LocalDate installmentDueDate = null;
|
||||
|
||||
LoanCharge latestCharge = getLatestLoanChargeWithSpecificDueDate(charges);
|
||||
@@ -867,7 +869,6 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null);
|
||||
installment.markAsAdditional();
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+72
-2
@@ -22,7 +22,6 @@ import static java.math.BigDecimal.ZERO;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.CHARGEBACK;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REAMORTIZE;
|
||||
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;
|
||||
@@ -44,11 +43,14 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
@@ -71,6 +73,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgingParameterRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
@@ -84,12 +88,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||
|
||||
public final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeProcessor = new SingleLoanChargeRepaymentScheduleProcessingWrapper();
|
||||
|
||||
private final LoanReAgingParameterRepository reAgingParameterRepository;
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
|
||||
@@ -144,6 +151,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove re-aged and additional (N+1) installments (if applicable), those will be recreated during the
|
||||
// reprocessing
|
||||
installments.removeIf(LoanRepaymentScheduleInstallment::isReAged);
|
||||
installments.removeIf(LoanRepaymentScheduleInstallment::isAdditional);
|
||||
|
||||
addChargeOnlyRepaymentInstallmentIfRequired(charges, installments);
|
||||
|
||||
@@ -185,6 +196,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
ctx.getOverpaymentHolder());
|
||||
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
||||
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||
// TODO: Cover rest of the transaction types
|
||||
default -> {
|
||||
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||
@@ -201,7 +213,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
.toList();
|
||||
List<LoanRepaymentScheduleInstallment> futureInstallments = installments.stream() //
|
||||
.filter(installment -> installment.getDueDate().isAfter(transactionDate)) //
|
||||
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment()) //
|
||||
.filter(installment -> !installment.isAdditional() && !installment.isDownPayment() && !installment.isReAged()) //
|
||||
.toList();
|
||||
|
||||
BigDecimal overallOverDuePrincipal = ZERO;
|
||||
@@ -1279,4 +1291,62 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
private Money aggregatedInterestPortion;
|
||||
private Money aggregatedPenaltyChargesPortion;
|
||||
}
|
||||
|
||||
private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
|
||||
MonetaryCurrency currency = ctx.getCurrency();
|
||||
List<LoanRepaymentScheduleInstallment> installments = ctx.getInstallments();
|
||||
// Either we have the transaction id or we need to fetch it from context
|
||||
Long loanTransactionId = loanTransaction.getId() != null ? loanTransaction.getId()
|
||||
: ctx.getChangedTransactionDetail().getCurrentTransactionToOldId().get(loanTransaction);
|
||||
LoanReAgeParameter reAgeParameter = reAgingParameterRepository.findByLoanTransactionId(loanTransactionId).orElseThrow();
|
||||
AtomicReference<Money> outstandingPrincipalBalance = new AtomicReference<>(Money.zero(currency));
|
||||
installments.forEach(i -> {
|
||||
Money principalOutstanding = i.getPrincipalOutstanding(currency);
|
||||
if (principalOutstanding.isGreaterThanZero()) {
|
||||
outstandingPrincipalBalance.set(outstandingPrincipalBalance.get().add(principalOutstanding));
|
||||
i.addToPrincipal(loanTransaction.getTransactionDate(), principalOutstanding.negated());
|
||||
}
|
||||
});
|
||||
|
||||
Money calculatedPrincipal = outstandingPrincipalBalance.get().dividedBy(reAgeParameter.getNumberOfInstallments(),
|
||||
MoneyHelper.getRoundingMode());
|
||||
Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf();
|
||||
if (installmentAmountInMultiplesOf != null) {
|
||||
calculatedPrincipal = Money.roundToMultiplesOf(calculatedPrincipal, installmentAmountInMultiplesOf);
|
||||
}
|
||||
Money adjustCalculatedPrincipal = outstandingPrincipalBalance.get()
|
||||
.minus(calculatedPrincipal.multipliedBy(reAgeParameter.getNumberOfInstallments()));
|
||||
LoanRepaymentScheduleInstallment lastNormalInstallment = installments.stream().filter(i -> !i.isDownPayment())
|
||||
.reduce((first, second) -> second).orElseThrow();
|
||||
LoanRepaymentScheduleInstallment reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(
|
||||
lastNormalInstallment.getLoan(), lastNormalInstallment.getInstallmentNumber() + 1, lastNormalInstallment.getDueDate(),
|
||||
reAgeParameter.getStartDate(), calculatedPrincipal.getAmount());
|
||||
installments.add(reAgedInstallment);
|
||||
for (int i = 1; i < reAgeParameter.getNumberOfInstallments(); i++) {
|
||||
LocalDate calculatedDueDate = calculateReAgedInstallmentDueDate(reAgeParameter, reAgedInstallment.getDueDate());
|
||||
reAgedInstallment = LoanRepaymentScheduleInstallment.newReAgedInstallment(reAgedInstallment.getLoan(),
|
||||
reAgedInstallment.getInstallmentNumber() + 1, reAgedInstallment.getDueDate(), calculatedDueDate,
|
||||
calculatedPrincipal.getAmount());
|
||||
installments.add(reAgedInstallment);
|
||||
}
|
||||
reAgedInstallment.addToPrincipal(loanTransaction.getTransactionDate(), adjustCalculatedPrincipal);
|
||||
|
||||
reprocessInstallmentsOrder(installments);
|
||||
}
|
||||
|
||||
private void reprocessInstallmentsOrder(List<LoanRepaymentScheduleInstallment> installments) {
|
||||
AtomicInteger counter = new AtomicInteger(0);
|
||||
installments.stream().sorted(LoanRepaymentScheduleInstallment::compareToByDueDate)
|
||||
.forEachOrdered(i -> i.updateInstallmentNumber(counter.getAndIncrement()));
|
||||
}
|
||||
|
||||
private LocalDate calculateReAgedInstallmentDueDate(LoanReAgeParameter reAgeParameter, LocalDate dueDate) {
|
||||
return switch (reAgeParameter.getFrequencyType()) {
|
||||
case DAYS -> dueDate.plusDays(reAgeParameter.getFrequencyNumber());
|
||||
case WEEKS -> dueDate.plusWeeks(reAgeParameter.getFrequencyNumber());
|
||||
case MONTHS -> dueDate.plusMonths(reAgeParameter.getFrequencyNumber());
|
||||
case YEARS -> dueDate.plusYears(reAgeParameter.getFrequencyNumber());
|
||||
default -> throw new UnsupportedOperationException(reAgeParameter.getFrequencyType().getCode());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -42,4 +42,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/1017_add_fee_and_penalty_adjustments_to_loan.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1018_rename_credited_principal_back_to_credits_amount.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1019_add_fixed_length.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1020_add_re_aged_flag_to_loan_installment.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<?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">
|
||||
<changeSet author="fineract" id="1">
|
||||
<addColumn tableName="m_loan_repayment_schedule">
|
||||
<column name="is_re_aged" type="boolean" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user