FINERACT-2060: Accrual reverse replay logic and Handling
This commit is contained in:
committed by
Adam Saghy
parent
8d25b0c765
commit
d57e72ec2f
+8
-1
@@ -19,17 +19,24 @@
|
||||
package org.apache.fineract.infrastructure.event.business.domain.loan;
|
||||
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
|
||||
|
||||
public class LoanStatusChangedBusinessEvent extends LoanBusinessEvent {
|
||||
|
||||
private static final String TYPE = "LoanStatusChangedBusinessEvent";
|
||||
private final LoanStatus oldStatus;
|
||||
|
||||
public LoanStatusChangedBusinessEvent(Loan value) {
|
||||
public LoanStatusChangedBusinessEvent(Loan value, LoanStatus oldStatus) {
|
||||
super(value);
|
||||
this.oldStatus = oldStatus;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
|
||||
public LoanStatus getOldStatus() {
|
||||
return oldStatus;
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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.infrastructure.event.business.domain.loan.transaction;
|
||||
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public class LoanAccrualAdjustmentTransactionBusinessEvent extends LoanTransactionBusinessEvent {
|
||||
|
||||
private static final String TYPE = "LoanAccrualAdjustmentTransactionBusinessEvent";
|
||||
|
||||
public LoanAccrualAdjustmentTransactionBusinessEvent(LoanTransaction value) {
|
||||
super(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType() {
|
||||
return TYPE;
|
||||
}
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@RequiredArgsConstructor
|
||||
public class AccrualChargeData {
|
||||
|
||||
private final Long loanChargeId;
|
||||
private final Long loanInstallmentChargeId;
|
||||
private final boolean isPenalty;
|
||||
private Money chargeAmount;
|
||||
private Money chargeAccruable;
|
||||
private Money chargeAccrued;
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@RequiredArgsConstructor
|
||||
public class AccrualPeriodData {
|
||||
|
||||
private final Integer installmentNumber;
|
||||
private final boolean isFirstPeriod;
|
||||
private final LocalDate startDate;
|
||||
private final LocalDate dueDate;
|
||||
private Money interestAmount;
|
||||
private Money interestAccruable;
|
||||
private Money interestAccrued;
|
||||
private Money unrecognizedWaive;
|
||||
private Money transactionAccrued;
|
||||
private final List<AccrualChargeData> charges = new ArrayList<>();
|
||||
|
||||
public AccrualPeriodData addCharge(AccrualChargeData charge) {
|
||||
charges.add(charge);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Money getChargeAmount() {
|
||||
return charges.stream().map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getFeeAmount() {
|
||||
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getPenaltyAmount() {
|
||||
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAmount).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getChargeAccrued() {
|
||||
return charges.stream().map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getFeeAccrued() {
|
||||
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getPenaltyAccrued() {
|
||||
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccrued).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getChargeAccruable() {
|
||||
return charges.stream().map(AccrualChargeData::getChargeAccruable).reduce(null, MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getFeeAccruable() {
|
||||
return charges.stream().filter(charge -> !charge.isPenalty()).map(AccrualChargeData::getChargeAccruable).reduce(null,
|
||||
MathUtil::plus);
|
||||
}
|
||||
|
||||
public Money getPenaltyAccruable() {
|
||||
return charges.stream().filter(AccrualChargeData::isPenalty).map(AccrualChargeData::getChargeAccruable).reduce(null,
|
||||
MathUtil::plus);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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.data;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
|
||||
@Data
|
||||
@Accessors(chain = true)
|
||||
@RequiredArgsConstructor
|
||||
public class AccrualPeriodsData {
|
||||
|
||||
private final MonetaryCurrency currency;
|
||||
private final List<AccrualPeriodData> periods = new ArrayList<>();
|
||||
|
||||
public AccrualPeriodsData addPeriod(AccrualPeriodData period) {
|
||||
periods.add(period);
|
||||
return this;
|
||||
}
|
||||
|
||||
public static AccrualPeriodsData create(@NotNull List<LoanRepaymentScheduleInstallment> installments, Integer firstInstallmentNumber,
|
||||
MonetaryCurrency currency) {
|
||||
AccrualPeriodsData accrualPeriods = new AccrualPeriodsData(currency);
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
Integer installmentNumber = installment.getInstallmentNumber();
|
||||
boolean isFirst = installmentNumber.equals(firstInstallmentNumber);
|
||||
accrualPeriods
|
||||
.addPeriod(new AccrualPeriodData(installmentNumber, isFirst, installment.getFromDate(), installment.getDueDate()));
|
||||
}
|
||||
return accrualPeriods;
|
||||
}
|
||||
|
||||
public AccrualPeriodData getPeriodByInstallmentNumber(Integer installmentNumber) {
|
||||
return installmentNumber == null ? null
|
||||
: periods.stream().filter(p -> installmentNumber.equals(p.getInstallmentNumber())).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
public Integer getFirstInstallmentNumber() {
|
||||
return periods.stream().filter(AccrualPeriodData::isFirstPeriod).map(AccrualPeriodData::getInstallmentNumber).findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -20,7 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
@@ -66,7 +66,7 @@ public class LoanChargeData {
|
||||
|
||||
private final BigDecimal amountOrPercentage;
|
||||
|
||||
private final Collection<ChargeData> chargeOptions;
|
||||
private final List<ChargeData> chargeOptions;
|
||||
|
||||
private final boolean penalty;
|
||||
|
||||
@@ -84,7 +84,7 @@ public class LoanChargeData {
|
||||
|
||||
private final BigDecimal maxCap;
|
||||
|
||||
private final Collection<LoanInstallmentChargeData> installmentChargeData;
|
||||
private final List<LoanInstallmentChargeData> installmentChargeData;
|
||||
|
||||
private BigDecimal amountAccrued;
|
||||
|
||||
@@ -94,7 +94,7 @@ public class LoanChargeData {
|
||||
|
||||
private final ExternalId externalLoanId;
|
||||
|
||||
public static LoanChargeData template(final Collection<ChargeData> chargeOptions) {
|
||||
public static LoanChargeData template(final List<ChargeData> chargeOptions) {
|
||||
return new LoanChargeData(null, null, null, null, null, null, null, null, chargeOptions, false, null, false, false, null,
|
||||
ExternalId.empty(), null, null, null, null, ExternalId.empty());
|
||||
}
|
||||
@@ -116,7 +116,7 @@ public class LoanChargeData {
|
||||
final LocalDate dueDate, final EnumOptionData chargeCalculationType, final BigDecimal percentage,
|
||||
final BigDecimal amountPercentageAppliedTo, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
||||
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
||||
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
this.id = id;
|
||||
this.chargeId = chargeId;
|
||||
this.name = name;
|
||||
@@ -160,9 +160,9 @@ public class LoanChargeData {
|
||||
|
||||
private LoanChargeData(final Long id, final Long chargeId, final String name, final CurrencyData currency, final BigDecimal amount,
|
||||
final BigDecimal percentage, final EnumOptionData chargeTimeType, final EnumOptionData chargeCalculationType,
|
||||
final Collection<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
||||
final List<ChargeData> chargeOptions, final boolean penalty, final EnumOptionData chargePaymentMode, final boolean paid,
|
||||
final boolean waived, final Long loanId, final ExternalId externalLoanId, final BigDecimal minCap, final BigDecimal maxCap,
|
||||
final BigDecimal amountOrPercentage, Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
final BigDecimal amountOrPercentage, List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
this.id = id;
|
||||
this.chargeId = chargeId;
|
||||
this.name = name;
|
||||
@@ -207,7 +207,7 @@ public class LoanChargeData {
|
||||
|
||||
public LoanChargeData(final Long id, final LocalDate dueAsOfDate, final LocalDate submittedOnDate, final BigDecimal amountOutstanding,
|
||||
EnumOptionData chargeTimeType, final Long loanId, final ExternalId externalLoanId,
|
||||
Collection<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
List<LoanInstallmentChargeData> installmentChargeData, final ExternalId externalId) {
|
||||
this.id = id;
|
||||
this.chargeId = null;
|
||||
this.name = null;
|
||||
@@ -308,7 +308,7 @@ public class LoanChargeData {
|
||||
this.externalId = chargeData.externalId;
|
||||
}
|
||||
|
||||
public LoanChargeData(LoanChargeData chargeData, Collection<LoanInstallmentChargeData> installmentChargeData) {
|
||||
public LoanChargeData(LoanChargeData chargeData, List<LoanInstallmentChargeData> installmentChargeData) {
|
||||
this.id = chargeData.id;
|
||||
this.chargeId = chargeData.chargeId;
|
||||
this.name = chargeData.name;
|
||||
|
||||
+2
@@ -61,6 +61,7 @@ public class LoanTransactionEnumData {
|
||||
private final boolean reAmortize;
|
||||
private final boolean accrualActivity;
|
||||
private final boolean interestRefund;
|
||||
private final boolean accrualAdjustment;
|
||||
|
||||
public LoanTransactionEnumData(final Long id, final String code, final String value) {
|
||||
this.id = id;
|
||||
@@ -96,6 +97,7 @@ public class LoanTransactionEnumData {
|
||||
this.reAge = Long.valueOf(LoanTransactionType.REAGE.getValue()).equals(this.id);
|
||||
this.reAmortize = Long.valueOf(LoanTransactionType.REAMORTIZE.getValue()).equals(this.id);
|
||||
this.interestRefund = Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
|
||||
this.accrualAdjustment = Long.valueOf(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue()).equals(this.id);
|
||||
}
|
||||
|
||||
public boolean isRepaymentType() {
|
||||
|
||||
+1
-1
@@ -55,7 +55,7 @@ public class DefaultLoanLifecycleStateMachine implements LoanLifecycleStateMachi
|
||||
|
||||
if (isNotLoanCreation(loanEvent)) {
|
||||
// in case of Loan creation, a LoanCreatedBusinessEvent is also raised, no need to send a status change
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan));
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanStatusChangedBusinessEvent(loan, oldStatus));
|
||||
}
|
||||
|
||||
// set mandatory field states based on new status after the transition
|
||||
|
||||
+83
-150
@@ -749,11 +749,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail reprocessTransactions() {
|
||||
ChangedTransactionDetail changedTransactionDetail;
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
@@ -778,10 +778,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public LoanTransaction handleChargeAppliedTransaction(final LoanCharge loanCharge, final LocalDate suppliedTransactionDate) {
|
||||
final Money chargeAmount = loanCharge.getAmount(getCurrency());
|
||||
Money feeCharges = chargeAmount;
|
||||
Money penaltyCharges = Money.zero(loanCurrency());
|
||||
Money penaltyCharges = Money.zero(getCurrency());
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
penaltyCharges = chargeAmount;
|
||||
feeCharges = Money.zero(loanCurrency());
|
||||
feeCharges = Money.zero(getCurrency());
|
||||
}
|
||||
|
||||
LocalDate transactionDate;
|
||||
@@ -898,8 +898,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
removeOrModifyTransactionAssociatedWithLoanChargeIfDueAtDisbursement(loanCharge);
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(getCurrency())) {
|
||||
/*
|
||||
* TODO Vishwas Currently we do not allow removing a loan charge after a loan is approved (hence there is no
|
||||
* need to adjust any loan transactions).
|
||||
@@ -907,9 +906,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||
* affected Transactions
|
||||
*/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
reprocessTransactions();
|
||||
}
|
||||
this.charges.remove(loanCharge);
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -922,7 +919,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
for (final LoanTransaction transaction : transactions) {
|
||||
if (transaction.isRepaymentAtDisbursement()
|
||||
&& doesLoanChargePaidByContainLoanCharge(transaction.getLoanChargesPaid(), loanCharge)) {
|
||||
final MonetaryCurrency currency = loanCurrency();
|
||||
final MonetaryCurrency currency = getCurrency();
|
||||
final Money chargeAmount = Money.of(currency, loanCharge.amount());
|
||||
if (transaction.isGreaterThan(chargeAmount)) {
|
||||
final Money principalPortion = Money.zero(currency);
|
||||
@@ -969,9 +966,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||
* affected Transactions
|
||||
*/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
reprocessTransactions();
|
||||
} else {
|
||||
// reprocess loan schedule based on charge been waived.
|
||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||
@@ -1067,7 +1062,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
final ExternalId externalId) {
|
||||
validateLoanIsNotClosed(loanCharge);
|
||||
|
||||
final Money amountWaived = loanCharge.waive(loanCurrency(), loanInstallmentNumber);
|
||||
final Money amountWaived = loanCharge.waive(getCurrency(), loanInstallmentNumber);
|
||||
changes.put("amount", amountWaived.getAmount());
|
||||
|
||||
Money unrecognizedIncome = amountWaived.zero();
|
||||
@@ -1089,10 +1084,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
}
|
||||
Money feeChargesWaived = chargeComponent;
|
||||
Money penaltyChargesWaived = Money.zero(loanCurrency());
|
||||
Money penaltyChargesWaived = Money.zero(getCurrency());
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
penaltyChargesWaived = chargeComponent;
|
||||
feeChargesWaived = Money.zero(loanCurrency());
|
||||
feeChargesWaived = Money.zero(getCurrency());
|
||||
}
|
||||
|
||||
LocalDate transactionDate = getDisbursementDate();
|
||||
@@ -1132,7 +1127,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
// Waive of charges whose due date falls after latest 'repayment' transaction don't require entire loan schedule
|
||||
// to be reprocessed.
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(loanCurrency())) {
|
||||
if (!loanCharge.isDueAtDisbursement() && loanCharge.isPaidOrPartiallyPaid(getCurrency())) {
|
||||
/*
|
||||
* TODO Vishwas Currently we do not allow waiving fully paid loan charge and waiving partially paid loan
|
||||
* charges only waives the remaining amount.
|
||||
@@ -1140,9 +1135,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* Consider removing this block of code or logically completing it for the future by getting the list of
|
||||
* affected Transactions
|
||||
*/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
reprocessTransactions();
|
||||
} else {
|
||||
// reprocess loan schedule based on charge been waived.
|
||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||
@@ -1289,7 +1282,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
addLoanRepaymentScheduleInstallment(installment);
|
||||
}
|
||||
}
|
||||
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
}
|
||||
@@ -1310,7 +1302,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
updateLoanScheduleDependentDerivedFields();
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment findByInstallmentNumber(Collection<LoanRepaymentScheduleInstallment> installments,
|
||||
@@ -1347,8 +1338,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.totalRecovered = recoveredAmount.getAmountDefaultedToNullIfZero();
|
||||
|
||||
final Money principal = this.loanRepaymentScheduleDetail.getPrincipal();
|
||||
this.summary.updateSummary(loanCurrency(), principal, getRepaymentScheduleInstallments(), this.loanSummaryWrapper,
|
||||
this.charges);
|
||||
this.summary.updateSummary(getCurrency(), principal, getRepaymentScheduleInstallments(), this.loanSummaryWrapper, this.charges);
|
||||
updateLoanOutstandingBalances();
|
||||
}
|
||||
}
|
||||
@@ -1923,7 +1913,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public void updateLoanRepaymentPeriodsDerivedFields(final LocalDate actualDisbursementDate) {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment repaymentPeriod : installments) {
|
||||
repaymentPeriod.updateObligationsMet(loanCurrency(), actualDisbursementDate);
|
||||
repaymentPeriod.updateObligationsMet(getCurrency(), actualDisbursementDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2002,7 +1992,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
* TODO Vishwas: do we need to be able to pass in payment type details for repayments at disbursements too?
|
||||
*/
|
||||
|
||||
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(loanCurrency());
|
||||
final Money totalFeeChargesDueAtDisbursement = this.summary.getTotalFeeChargesDueAtDisbursement(getCurrency());
|
||||
/*
|
||||
* all Charges repaid at disbursal is marked as repaid and "APPLY Charge" transactions are created for all other
|
||||
* fees ( which are created during disbursal but not repaid)
|
||||
@@ -2221,7 +2211,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
if (charge.isOverdueInstallmentCharge()) {
|
||||
charge.setActive(false);
|
||||
} else {
|
||||
charge.resetToOriginal(loanCurrency());
|
||||
charge.resetToOriginal(getCurrency());
|
||||
}
|
||||
}
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
@@ -2354,7 +2344,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
loanTransaction.updateLoan(this);
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
if (loanTransaction.isNotZero()) {
|
||||
addLoanTransaction(loanTransaction);
|
||||
}
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -2371,7 +2361,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
if (loanTransaction.isRecoveryRepayment()
|
||||
&& loanTransaction.getAmount(loanCurrency()).getAmount().compareTo(getSummary().getTotalWrittenOff()) > 0) {
|
||||
&& loanTransaction.getAmount(getCurrency()).getAmount().compareTo(getSummary().getTotalWrittenOff()) > 0) {
|
||||
final String errorMessage = "The transaction amount cannot greater than the remaining written off amount.";
|
||||
throw new InvalidLoanStateTransitionException("transaction", "cannot.be.greater.than.total.written.off", errorMessage);
|
||||
}
|
||||
@@ -2380,7 +2370,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
final boolean isTransactionChronologicallyLatest = isChronologicallyLatestRepaymentOrWaiver(loanTransaction);
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
if (loanTransaction.isNotZero()) {
|
||||
addLoanTransaction(loanTransaction);
|
||||
}
|
||||
|
||||
@@ -2400,12 +2390,12 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
if (loanTransaction.isInterestWaiver()) {
|
||||
Money totalInterestOutstandingOnLoan = getTotalInterestOutstandingOnLoan();
|
||||
if (adjustedTransaction != null) {
|
||||
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(loanCurrency()));
|
||||
totalInterestOutstandingOnLoan = totalInterestOutstandingOnLoan.plus(adjustedTransaction.getAmount(getCurrency()));
|
||||
}
|
||||
if (loanTransaction.getAmount(loanCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
|
||||
if (loanTransaction.getAmount(getCurrency()).isGreaterThan(totalInterestOutstandingOnLoan)) {
|
||||
final String errorMessage = "The amount of interest to waive cannot be greater than total interest outstanding on loan.";
|
||||
throw new InvalidLoanStateTransitionException("waive.interest", "amount.exceeds.total.outstanding.interest", errorMessage,
|
||||
loanTransaction.getAmount(loanCurrency()), totalInterestOutstandingOnLoan.getAmount());
|
||||
loanTransaction.getAmount(getCurrency()), totalInterestOutstandingOnLoan.getAmount());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2450,17 +2440,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
&& !getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
/*
|
||||
* Commented since throwing exception if external id present for one of the transactions. for this need to
|
||||
* save the reversed transactions first and then new transactions.
|
||||
*/
|
||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
changedTransactionDetail = reprocessTransactions();
|
||||
}
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -2531,7 +2511,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
// FIXME - kw - update account balance to negative amount.
|
||||
handleLoanOverpayment(transactionDate, loanLifecycleStateMachine);
|
||||
statusChanged = true;
|
||||
} else if (this.summary.isRepaidInFull(loanCurrency())) {
|
||||
} else if (this.summary.isRepaidInFull(getCurrency())) {
|
||||
handleLoanRepaymentInFull(transactionDate, loanLifecycleStateMachine);
|
||||
statusChanged = true;
|
||||
} else {
|
||||
@@ -2645,7 +2625,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
LocalDate lastTransactionDate = null;
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if (transaction.isRepaymentLikeType() && transaction.isNonZero()) {
|
||||
if (transaction.isRepaymentLikeType() && transaction.isGreaterThanZero()) {
|
||||
lastTransactionDate = transaction.getTransactionDate();
|
||||
}
|
||||
}
|
||||
@@ -2669,7 +2649,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||
|
||||
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
|
||||
final Money outstandingForPeriod = scheduledRepayment.getInterestOutstanding(getCurrency());
|
||||
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate()) && scheduledRepayment.isNotFullyPaidOff()
|
||||
&& outstandingForPeriod.isGreaterThanZero()) {
|
||||
transactionDate = scheduledRepayment.getDueDate();
|
||||
@@ -2692,16 +2672,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
final LoanTransaction writeOffTransaction = findWriteOffTransaction();
|
||||
writeOffTransaction.reverse();
|
||||
loanLifecycleStateMachine.transition(LoanEvent.WRITE_OFF_OUTSTANDING_UNDO, this);
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
updateLoanSummaryDerivedFields();
|
||||
return changedTransactionDetail;
|
||||
return reprocessTransactions();
|
||||
}
|
||||
|
||||
public LoanTransaction findWriteOffTransaction() {
|
||||
@@ -2718,7 +2689,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
private Money calculateTotalOverpayment() {
|
||||
Money totalPaidInRepayments = getTotalPaidInRepayments();
|
||||
|
||||
final MonetaryCurrency currency = loanCurrency();
|
||||
final MonetaryCurrency currency = getCurrency();
|
||||
Money cumulativeTotalPaidOnInstallments = Money.zero(currency);
|
||||
Money cumulativeTotalWaivedOnInstallments = Money.zero(currency);
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
@@ -2759,10 +2730,6 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return getTotalRecoveredPayments();
|
||||
}
|
||||
|
||||
public MonetaryCurrency loanCurrency() {
|
||||
return this.loanRepaymentScheduleDetail.getCurrency();
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail closeAsWrittenOff(final JsonCommand command, final LoanLifecycleStateMachine loanLifecycleStateMachine,
|
||||
final Map<String, Object> changes, final List<Long> existingTransactionIds, final List<Long> existingReversedTransactionIds,
|
||||
final AppUser currentUser, final ScheduleGeneratorDTO scheduleGeneratorDTO) {
|
||||
@@ -2822,7 +2789,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
addLoanTransaction(loanTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -2844,14 +2811,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
addLoanTransaction(mapEntry.getValue());
|
||||
}
|
||||
updateLoanSummaryDerivedFields();
|
||||
changedTransactionDetail = reprocessTransactions();
|
||||
LocalDate lastLoanTransactionDate = getLatestTransactionDate();
|
||||
doPostLoanTransactionChecks(lastLoanTransactionDate, loanLifecycleStateMachine);
|
||||
}
|
||||
@@ -2904,7 +2864,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
LoanTransaction loanTransaction = null;
|
||||
if (isOpen()) {
|
||||
final Money totalOutstanding = this.summary.getTotalOutstanding(loanCurrency());
|
||||
final Money totalOutstanding = this.summary.getTotalOutstanding(getCurrency());
|
||||
if (totalOutstanding.isGreaterThanZero() && getInArrearsTolerance().isGreaterThanOrEqualTo(totalOutstanding)) {
|
||||
|
||||
this.closedOnDate = closureDate;
|
||||
@@ -2923,7 +2883,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
addLoanTransaction(loanTransaction);
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(loanCurrency(),
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -3123,11 +3083,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
private Money getTotalPaidInRepayments() {
|
||||
Money cumulativePaid = Money.zero(loanCurrency());
|
||||
Money cumulativePaid = Money.zero(getCurrency());
|
||||
|
||||
for (final LoanTransaction repayment : this.loanTransactions) {
|
||||
if (repayment.isRepaymentLikeType() && !repayment.isReversed()) {
|
||||
cumulativePaid = cumulativePaid.plus(repayment.getAmount(loanCurrency()));
|
||||
cumulativePaid = cumulativePaid.plus(repayment.getAmount(getCurrency()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3135,11 +3095,11 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public Money getTotalRecoveredPayments() {
|
||||
Money cumulativePaid = Money.zero(loanCurrency());
|
||||
Money cumulativePaid = Money.zero(getCurrency());
|
||||
|
||||
for (final LoanTransaction recoveredPayment : this.loanTransactions) {
|
||||
if (recoveredPayment.isRecoveryRepayment()) {
|
||||
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(loanCurrency()));
|
||||
cumulativePaid = cumulativePaid.plus(recoveredPayment.getAmount(getCurrency()));
|
||||
}
|
||||
}
|
||||
return cumulativePaid;
|
||||
@@ -3148,16 +3108,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public Money getTotalPrincipalOutstandingUntil(LocalDate date) {
|
||||
return getRepaymentScheduleInstallments().stream()
|
||||
.filter(installment -> installment.getDueDate().isBefore(date) || installment.getDueDate().isEqual(date))
|
||||
.map(installment -> installment.getPrincipalOutstanding(loanCurrency())).reduce(Money.zero(loanCurrency()), Money::add);
|
||||
.map(installment -> installment.getPrincipalOutstanding(getCurrency())).reduce(Money.zero(getCurrency()), Money::add);
|
||||
|
||||
}
|
||||
|
||||
private Money getTotalInterestOutstandingOnLoan() {
|
||||
Money cumulativeInterest = Money.zero(loanCurrency());
|
||||
Money cumulativeInterest = Money.zero(getCurrency());
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
|
||||
cumulativeInterest = cumulativeInterest.plus(scheduledRepayment.getInterestOutstanding(getCurrency()));
|
||||
}
|
||||
|
||||
return cumulativeInterest;
|
||||
@@ -3169,7 +3129,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
List<LoanRepaymentScheduleInstallment> installments = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : installments) {
|
||||
|
||||
final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(loanCurrency());
|
||||
final Money interestOutstandingForPeriod = scheduledRepayment.getInterestOutstanding(getCurrency());
|
||||
if (scheduledRepayment.isOverdueOn(DateUtils.getBusinessLocalDate())) {
|
||||
cumulativeInterestOverdue = cumulativeInterestOverdue.plus(interestOutstandingForPeriod);
|
||||
}
|
||||
@@ -3515,7 +3475,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
&& !DateUtils.isAfter(transaction.getTransactionDate(), tillDate)) {
|
||||
if (transaction.isAccrual()) {
|
||||
receivableInterest = receivableInterest.plus(transaction.getInterestPortion(getCurrency()));
|
||||
} else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver()) {
|
||||
} else if (transaction.isRepaymentLikeType() || transaction.isInterestWaiver() || transaction.isAccrualAdjustment()) {
|
||||
receivableInterest = receivableInterest.minus(transaction.getInterestPortion(getCurrency()));
|
||||
}
|
||||
}
|
||||
@@ -3841,8 +3801,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public LocalDate getLastUserTransactionDate() {
|
||||
LocalDate currentTransactionDate = getDisbursementDate();
|
||||
for (final LoanTransaction previousTransaction : this.loanTransactions) {
|
||||
if (!(previousTransaction.isReversed() || previousTransaction.isAccrual() || previousTransaction.isIncomePosting()
|
||||
|| previousTransaction.isAccrualActivity())
|
||||
if (!(previousTransaction.isReversed() || previousTransaction.isAccrualRelated() || previousTransaction.isIncomePosting())
|
||||
&& DateUtils.isBefore(currentTransactionDate, previousTransaction.getTransactionDate())) {
|
||||
currentTransactionDate = previousTransaction.getTransactionDate();
|
||||
}
|
||||
@@ -4131,17 +4090,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
regenerateRepaymentSchedule(scheduleGeneratorDTO);
|
||||
}
|
||||
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
addLoanTransaction(mapEntry.getValue());
|
||||
}
|
||||
|
||||
return changedTransactionDetail;
|
||||
return reprocessTransactions();
|
||||
}
|
||||
|
||||
public BigDecimal getPrincipalAmountForRepaymentSchedule() {
|
||||
@@ -4219,7 +4168,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
} else {
|
||||
regenerateRepaymentSchedule(generatorDTO);
|
||||
}
|
||||
return processTransactions();
|
||||
return reprocessTransactions();
|
||||
|
||||
}
|
||||
|
||||
@@ -4229,33 +4178,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
} else {
|
||||
regenerateRepaymentSchedule(generatorDTO);
|
||||
}
|
||||
return processTransactions();
|
||||
return reprocessTransactions();
|
||||
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail handleRegenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(generatorDTO);
|
||||
return processTransactions();
|
||||
|
||||
}
|
||||
|
||||
public ChangedTransactionDetail processTransactions() {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = getTransactionProcessor();
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
/*
|
||||
* Commented since throwing exception if external id present for one of the transactions. for this need to save
|
||||
* the reversed transactions first and then new transactions.
|
||||
*/
|
||||
this.loanTransactions.addAll(changedTransactionDetail.getNewTransactionMappings().values());
|
||||
updateLoanSummaryDerivedFields();
|
||||
|
||||
return changedTransactionDetail;
|
||||
return reprocessTransactions();
|
||||
}
|
||||
|
||||
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO) {
|
||||
@@ -4417,17 +4346,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
private OutstandingAmountsDTO getTotalOutstandingOnLoan() {
|
||||
Money totalPrincipal = Money.zero(loanCurrency());
|
||||
Money totalInterest = Money.zero(loanCurrency());
|
||||
Money feeCharges = Money.zero(loanCurrency());
|
||||
Money penaltyCharges = Money.zero(loanCurrency());
|
||||
Money totalPrincipal = Money.zero(getCurrency());
|
||||
Money totalInterest = Money.zero(getCurrency());
|
||||
Money feeCharges = Money.zero(getCurrency());
|
||||
Money penaltyCharges = Money.zero(getCurrency());
|
||||
final Set<LoanInterestRecalcualtionAdditionalDetails> compoundingDetails = null;
|
||||
List<LoanRepaymentScheduleInstallment> repaymentSchedule = getRepaymentScheduleInstallments();
|
||||
for (final LoanRepaymentScheduleInstallment scheduledRepayment : repaymentSchedule) {
|
||||
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(loanCurrency()));
|
||||
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(loanCurrency()));
|
||||
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(loanCurrency()));
|
||||
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(loanCurrency()));
|
||||
totalPrincipal = totalPrincipal.plus(scheduledRepayment.getPrincipalOutstanding(getCurrency()));
|
||||
totalInterest = totalInterest.plus(scheduledRepayment.getInterestOutstanding(getCurrency()));
|
||||
feeCharges = feeCharges.plus(scheduledRepayment.getFeeChargesOutstanding(getCurrency()));
|
||||
penaltyCharges = penaltyCharges.plus(scheduledRepayment.getPenaltyChargesOutstanding(getCurrency()));
|
||||
}
|
||||
return new OutstandingAmountsDTO(totalPrincipal.getCurrency()).principal(totalPrincipal).interest(totalInterest)
|
||||
.feeCharges(feeCharges).penaltyCharges(penaltyCharges);
|
||||
@@ -4533,6 +4462,16 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return getRepaymentScheduleInstallments().stream().filter(predicate).findFirst().orElse(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param predicate
|
||||
* filter of the installments
|
||||
* @return the installments matching the filter
|
||||
**/
|
||||
public List<LoanRepaymentScheduleInstallment> getRepaymentScheduleInstallments(
|
||||
@NotNull Predicate<LoanRepaymentScheduleInstallment> predicate) {
|
||||
return getRepaymentScheduleInstallments().stream().filter(predicate).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return loan disbursement data
|
||||
**/
|
||||
@@ -4691,17 +4630,18 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.createStandingInstructionAtDisbursement != null && this.createStandingInstructionAtDisbursement;
|
||||
}
|
||||
|
||||
public Collection<LoanCharge> getLoanCharges(LocalDate dueDate) {
|
||||
Collection<LoanCharge> loanCharges = new ArrayList<>();
|
||||
public List<LoanCharge> getLoanChargesByDueDate(LocalDate dueDate) {
|
||||
return getLoanCharges(
|
||||
loanCharge -> loanCharge.getDueLocalDate() != null && DateUtils.isEqual(loanCharge.getDueLocalDate(), dueDate));
|
||||
}
|
||||
|
||||
for (LoanCharge loanCharge : charges) {
|
||||
|
||||
if (loanCharge.getDueLocalDate() != null && loanCharge.getDueLocalDate().equals(dueDate)) {
|
||||
loanCharges.add(loanCharge);
|
||||
}
|
||||
}
|
||||
|
||||
return loanCharges;
|
||||
/**
|
||||
* @param predicate
|
||||
* filter of the charges
|
||||
* @return the loan charges matching the filter
|
||||
**/
|
||||
public List<LoanCharge> getLoanCharges(@NotNull Predicate<LoanCharge> predicate) {
|
||||
return getLoanCharges().stream().filter(predicate).toList();
|
||||
}
|
||||
|
||||
public void setGuaranteeAmount(BigDecimal guaranteeAmountDerived) {
|
||||
@@ -4796,7 +4736,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);
|
||||
}
|
||||
|
||||
if (loanTransaction.isNotZero(loanCurrency())) {
|
||||
if (loanTransaction.isNotZero()) {
|
||||
addLoanTransaction(loanTransaction);
|
||||
}
|
||||
if (loanTransaction.isNotRefundForActiveLoan()) {
|
||||
@@ -4828,13 +4768,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
loanRepaymentScheduleTransactionProcessor.processLatestTransaction(loanTransaction, new TransactionCtx(getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), new MoneyHolder(getTotalOverpaidAsMoney()), null));
|
||||
} else {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsForReprocessing();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
}
|
||||
|
||||
changedTransactionDetail = reprocessTransactions();
|
||||
}
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -4869,7 +4803,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
LocalDate lastTransactionDate = null;
|
||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||
if ((transaction.isRepaymentLikeType() || transaction.isRefundForActiveLoan() || transaction.isCreditBalanceRefund())
|
||||
&& transaction.isNonZero() && transaction.isNotReversed()) {
|
||||
&& transaction.isGreaterThanZero() && transaction.isNotReversed()) {
|
||||
lastTransactionDate = transaction.getTransactionDate();
|
||||
}
|
||||
}
|
||||
@@ -4979,7 +4913,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
charge.setActive(false);
|
||||
} else if (charge.isTrancheDisbursementCharge() && disbursementDetail.getDisbursementDate()
|
||||
.equals(charge.getTrancheDisbursementCharge().getloanDisbursementDetails().actualDisbursementDate())) {
|
||||
charge.resetToOriginal(loanCurrency());
|
||||
charge.resetToOriginal(getCurrency());
|
||||
}
|
||||
}
|
||||
this.loanRepaymentScheduleDetail.setPrincipal(getDisbursedAmount().subtract(disbursementDetail.principal()));
|
||||
@@ -5211,7 +5145,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public void validateForForeclosure(final LocalDate transactionDate) {
|
||||
if (isInterestRecalculationEnabledForProduct()) {
|
||||
if (getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
|
||||
final String defaultUserMessage = "The loan with interest recalculation enabled cannot be foreclosed.";
|
||||
throw new LoanForeclosureException("loan.with.interest.recalculation.enabled.cannot.be.foreclosured", defaultUserMessage,
|
||||
getId());
|
||||
@@ -5461,8 +5395,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
public LoanTransaction getLastUserTransaction() {
|
||||
return getLoanTransactions().stream() //
|
||||
.filter(LoanTransaction::isNotReversed) //
|
||||
.filter(t -> !(t.isAccrualTransaction() || t.isIncomePosting())) //
|
||||
.filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting())) //
|
||||
.reduce((first, second) -> second) //
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
+17
-53
@@ -28,6 +28,7 @@ import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.UniqueConstraint;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
@@ -40,7 +41,8 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.infrastructure.core.data.EnumOptionData;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
@@ -58,6 +60,7 @@ import org.apache.fineract.portfolio.loanaccount.data.LoanChargeData;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidDetail;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanInstallmentChargeData;
|
||||
|
||||
@Getter
|
||||
@Entity
|
||||
@Table(name = "m_loan_charge", uniqueConstraints = { @UniqueConstraint(columnNames = { "external_id" }, name = "external_id") })
|
||||
public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
@@ -305,10 +308,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
}
|
||||
|
||||
public BigDecimal getAmountPercentageAppliedTo() {
|
||||
return this.amountPercentageAppliedTo;
|
||||
}
|
||||
|
||||
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
|
||||
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
|
||||
}
|
||||
@@ -509,15 +508,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public LocalDate getDueLocalDate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
public LocalDate getDueDate() {
|
||||
return this.dueDate;
|
||||
}
|
||||
|
||||
public LocalDate getSubmittedOnDate() {
|
||||
return submittedOnDate;
|
||||
return this.dueDate; // TODO delete duplicated method
|
||||
}
|
||||
|
||||
private boolean determineIfFullyPaid() {
|
||||
@@ -597,11 +588,11 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public BigDecimal amount() {
|
||||
return this.amount;
|
||||
return this.amount; // TODO delete duplicated method
|
||||
}
|
||||
|
||||
public BigDecimal amountOutstanding() {
|
||||
return this.amountOutstanding;
|
||||
return this.amountOutstanding; // TODO delete duplicated method
|
||||
}
|
||||
|
||||
public Money getAmountOutstanding(final MonetaryCurrency currency) {
|
||||
@@ -644,14 +635,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.waived;
|
||||
}
|
||||
|
||||
public BigDecimal getMinCap() {
|
||||
return this.minCap;
|
||||
}
|
||||
|
||||
public BigDecimal getMaxCap() {
|
||||
return this.maxCap;
|
||||
}
|
||||
|
||||
public boolean isPaidOrPartiallyPaid(final MonetaryCurrency currency) {
|
||||
|
||||
final Money amountWaivedOrWrittenOff = getAmountWaived(currency).plus(getAmountWrittenOff(currency));
|
||||
@@ -729,10 +712,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return this.charge.getCurrencyCode();
|
||||
}
|
||||
|
||||
public Charge getCharge() {
|
||||
return this.charge;
|
||||
}
|
||||
|
||||
/*
|
||||
* @Override public boolean equals(final Object obj) { if (obj == null) { return false; } if (obj == this) { return
|
||||
* true; } if (obj.getClass() != getClass()) { return false; } final LoanCharge rhs = (LoanCharge) obj; return new
|
||||
@@ -756,10 +735,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return ChargeCalculationType.fromInt(this.chargeCalculation);
|
||||
}
|
||||
|
||||
public BigDecimal getPercentage() {
|
||||
return this.percentage;
|
||||
}
|
||||
|
||||
public void updateAmount(final BigDecimal amount) {
|
||||
this.amount = amount;
|
||||
calculateOutstanding();
|
||||
@@ -851,7 +826,7 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
}
|
||||
|
||||
public BigDecimal amountOrPercentage() {
|
||||
return this.amountOrPercentage;
|
||||
return this.amountOrPercentage; // TODO delete duplicated method
|
||||
}
|
||||
|
||||
public BigDecimal chargeAmount() {
|
||||
@@ -906,12 +881,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
}
|
||||
|
||||
public LoanOverdueInstallmentCharge getOverdueInstallmentCharge() {
|
||||
return this.overdueInstallmentCharge;
|
||||
}
|
||||
|
||||
public LoanTrancheDisbursementCharge getTrancheDisbursementCharge() {
|
||||
return this.loanTrancheDisbursementCharge;
|
||||
return this.loanTrancheDisbursementCharge; // TODO delete duplicated method
|
||||
}
|
||||
|
||||
public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final Integer installmentNumber, final Money feeAmount) {
|
||||
@@ -960,14 +931,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return paidChargePerInstallment;
|
||||
}
|
||||
|
||||
public Set<LoanChargePaidBy> getLoanChargePaidBySet() {
|
||||
return this.loanChargePaidBySet;
|
||||
}
|
||||
|
||||
public Loan getLoan() {
|
||||
return this.loan;
|
||||
}
|
||||
|
||||
public boolean isDisbursementCharge() {
|
||||
return ChargeTimeType.fromInt(this.chargeTime).equals(ChargeTimeType.DISBURSEMENT);
|
||||
}
|
||||
@@ -988,10 +951,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
this.waived = false;
|
||||
}
|
||||
|
||||
public ExternalId getExternalId() {
|
||||
return externalId;
|
||||
}
|
||||
|
||||
public ChargeTimeType getChargeTimeType() {
|
||||
return ChargeTimeType.fromInt(this.chargeTime);
|
||||
}
|
||||
@@ -1013,6 +972,11 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
return dueDate;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<LoanChargePaidBy> getLoanChargePaidBy(@NotNull Predicate<LoanChargePaidBy> filter) {
|
||||
return getLoanChargePaidBySet().stream().filter(filter).toList();
|
||||
}
|
||||
|
||||
public LoanChargeData toData() {
|
||||
EnumOptionData chargeTimeTypeData = new EnumOptionData((long) getChargeTimeType().ordinal(), getChargeTimeType().getCode(),
|
||||
String.valueOf(getChargeTimeType().getValue()));
|
||||
@@ -1020,8 +984,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
getChargeCalculation().getCode(), String.valueOf(getChargeCalculation().getValue()));
|
||||
EnumOptionData chargePaymentModeData = new EnumOptionData((long) getChargePaymentMode().ordinal(), getChargePaymentMode().getCode(),
|
||||
String.valueOf(getChargePaymentMode().getValue()));
|
||||
Set<LoanInstallmentChargeData> loanInstallmentChargeDataSet = installmentCharges().stream().map(LoanInstallmentCharge::toData)
|
||||
.collect(Collectors.toSet());
|
||||
List<LoanInstallmentChargeData> loanInstallmentChargeDataList = installmentCharges().stream().map(LoanInstallmentCharge::toData)
|
||||
.toList();
|
||||
|
||||
return LoanChargeData.builder().id(getId()).chargeId(getCharge().getId()).name(getCharge().getName())
|
||||
.currency(getCharge().toData().getCurrency()).amount(amount).amountPaid(amountPaid).amountWaived(amountWaived)
|
||||
@@ -1029,6 +993,6 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
.submittedOnDate(submittedOnDate).dueDate(dueDate).chargeCalculationType(chargeCalculationTypeData).percentage(percentage)
|
||||
.amountPercentageAppliedTo(amountPercentageAppliedTo).amountOrPercentage(amountOrPercentage).penalty(penaltyCharge)
|
||||
.chargePaymentMode(chargePaymentModeData).paid(paid).waived(waived).loanId(loan.getId()).minCap(minCap).maxCap(maxCap)
|
||||
.installmentChargeData(loanInstallmentChargeDataSet).externalId(externalId).build();
|
||||
.installmentChargeData(loanInstallmentChargeDataList).externalId(externalId).build();
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -188,7 +188,7 @@ public class LoanInterestRecalculationDetails extends AbstractPersistableCustom<
|
||||
}
|
||||
|
||||
public boolean isCompoundingToBePostedAsTransaction() {
|
||||
return null == this.isCompoundingToBePostedAsTransaction ? false : this.isCompoundingToBePostedAsTransaction;
|
||||
return this.isCompoundingToBePostedAsTransaction != null && this.isCompoundingToBePostedAsTransaction;
|
||||
}
|
||||
|
||||
public boolean allowCompoundingOnEod() {
|
||||
|
||||
+19
-15
@@ -34,6 +34,7 @@ import java.util.Set;
|
||||
import lombok.Getter;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
@@ -692,29 +693,32 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
||||
|
||||
public void updateChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
||||
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
||||
this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.getAmount());
|
||||
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.getAmount());
|
||||
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.getAmount());
|
||||
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.getAmount());
|
||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.getAmount());
|
||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.getAmount());
|
||||
this.feeChargesCharged = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesDue));
|
||||
this.feeChargesWaived = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesWaived));
|
||||
this.feeChargesWrittenOff = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeChargesWrittenOff));
|
||||
this.penaltyCharges = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesDue));
|
||||
this.penaltyChargesWaived = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesWaived));
|
||||
this.penaltyChargesWrittenOff = MathUtil.zeroToNull(MathUtil.toBigDecimal(penaltyChargesWrittenOff));
|
||||
}
|
||||
|
||||
public void addToChargePortion(final Money feeChargesDue, final Money feeChargesWaived, final Money feeChargesWrittenOff,
|
||||
final Money penaltyChargesDue, final Money penaltyChargesWaived, final Money penaltyChargesWrittenOff) {
|
||||
this.feeChargesCharged = defaultToNullIfZero(feeChargesDue.plus(this.feeChargesCharged).getAmount());
|
||||
this.feeChargesWaived = defaultToNullIfZero(feeChargesWaived.plus(this.feeChargesWaived).getAmount());
|
||||
this.feeChargesWrittenOff = defaultToNullIfZero(feeChargesWrittenOff.plus(this.feeChargesWrittenOff).getAmount());
|
||||
this.penaltyCharges = defaultToNullIfZero(penaltyChargesDue.plus(this.penaltyCharges).getAmount());
|
||||
this.penaltyChargesWaived = defaultToNullIfZero(penaltyChargesWaived.plus(this.penaltyChargesWaived).getAmount());
|
||||
this.penaltyChargesWrittenOff = defaultToNullIfZero(penaltyChargesWrittenOff.plus(this.penaltyChargesWrittenOff).getAmount());
|
||||
this.feeChargesCharged = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesDue), this.feeChargesCharged));
|
||||
this.feeChargesWaived = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesWaived), this.feeChargesWaived));
|
||||
this.feeChargesWrittenOff = MathUtil
|
||||
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(feeChargesWrittenOff), this.feeChargesWrittenOff));
|
||||
this.penaltyCharges = MathUtil.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesDue), this.penaltyCharges));
|
||||
this.penaltyChargesWaived = MathUtil
|
||||
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesWaived), this.penaltyChargesWaived));
|
||||
this.penaltyChargesWrittenOff = MathUtil
|
||||
.zeroToNull(MathUtil.add(MathUtil.toBigDecimal(penaltyChargesWrittenOff), this.penaltyChargesWrittenOff));
|
||||
checkIfRepaymentPeriodObligationsAreMet(getObligationsMetOnDate(), feeChargesDue.getCurrency());
|
||||
}
|
||||
|
||||
public void updateAccrualPortion(final Money interest, final Money feeCharges, final Money penalityCharges) {
|
||||
this.interestAccrued = defaultToNullIfZero(interest.getAmount());
|
||||
this.feeAccrued = defaultToNullIfZero(feeCharges.getAmount());
|
||||
this.penaltyAccrued = defaultToNullIfZero(penalityCharges.getAmount());
|
||||
this.interestAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(interest));
|
||||
this.feeAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(feeCharges));
|
||||
this.penaltyAccrued = MathUtil.zeroToNull(MathUtil.toBigDecimal(penalityCharges));
|
||||
}
|
||||
|
||||
public void updateObligationsMet(final MonetaryCurrency currency, final LocalDate transactionDate) {
|
||||
|
||||
+9
@@ -253,6 +253,15 @@ public class LoanRepaymentScheduleProcessingWrapper {
|
||||
: DateUtils.isDateInRangeFromExclusiveToInclusive(targetDate, fromDate, toDate);
|
||||
}
|
||||
|
||||
public static boolean isBeforePeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment, boolean isFirstPeriod) {
|
||||
LocalDate fromDate = installment.getFromDate();
|
||||
return isFirstPeriod ? DateUtils.isBefore(targetDate, fromDate) : !DateUtils.isAfter(targetDate, fromDate);
|
||||
}
|
||||
|
||||
public static boolean isAfterPeriod(LocalDate targetDate, LoanRepaymentScheduleInstallment installment) {
|
||||
return DateUtils.isAfter(targetDate, installment.getDueDate());
|
||||
}
|
||||
|
||||
public static Optional<LoanRepaymentScheduleInstallment> findInPeriod(LocalDate targetDate,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
int firstNumber = fetchFirstNormalInstallmentNumber(installments);
|
||||
|
||||
+20
@@ -101,6 +101,18 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
||||
|
||||
String FIND_ALL_LOAN_IDS_BY_STATUS_ID = "SELECT loan.id FROM Loan loan WHERE loan.loanStatus = :statusId";
|
||||
|
||||
String LOANS_FOR_ACCRUAL = "select l from Loan l left join l.loanInterestRecalculationDetails recalcDetails "
|
||||
+ "where l.loanStatus = 300 and l.isNpa = false and l.chargedOff = false "
|
||||
+ "and l.loanProduct.accountingRule = :accountingType "
|
||||
+ "and (recalcDetails.isCompoundingToBePostedAsTransaction is null or recalcDetails.isCompoundingToBePostedAsTransaction = false) "
|
||||
+ "and (exists (select ls.id from LoanRepaymentScheduleInstallment ls where ls.loan.id = l.id and ls.isDownPayment = false "
|
||||
+ "and ((coalesce(ls.interestCharged, 0) - coalesce(ls.interestWaived, 0)) <> coalesce(ls.interestAccrued, 0) "
|
||||
+ "or (coalesce(ls.feeChargesCharged, 0) - coalesce(ls.feeChargesWaived, 0)) <> coalesce(ls.feeAccrued, 0) "
|
||||
+ "or (coalesce(ls.penaltyCharges, 0) - coalesce(ls.penaltyChargesWaived, 0)) <> coalesce(ls.penaltyAccrued, 0)) ";
|
||||
String FIND_LOANS_FOR_PERIODIC_ACCRUAL = LOANS_FOR_ACCRUAL
|
||||
+ "and (:futureCharges = true or ls.fromDate < :tillDate or (ls.installmentNumber = (select min(lsi.installmentNumber) from LoanRepaymentScheduleInstallment lsi where lsi.loan.id = l.id and lsi.isDownPayment = false) and ls.fromDate = :tillDate))))";
|
||||
String FIND_LOANS_FOR_ADD_ACCRUAL = LOANS_FOR_ACCRUAL + "and (:futureCharges = true or ls.dueDate <= :tillDate)))";
|
||||
|
||||
@Query(FIND_GROUP_LOANS_DISBURSED_AFTER)
|
||||
List<Loan> getGroupLoansDisbursedAfter(@Param("disbursementDate") LocalDate disbursementDate, @Param("groupId") Long groupId,
|
||||
@Param("loanType") Integer loanType);
|
||||
@@ -227,4 +239,12 @@ public interface LoanRepository extends JpaRepository<Loan, Long>, JpaSpecificat
|
||||
|
||||
@Query(FIND_ALL_LOAN_IDS_BY_STATUS_ID)
|
||||
List<Long> findLoanIdByStatusId(@Param("statusId") Integer statusId);
|
||||
|
||||
@Query(FIND_LOANS_FOR_PERIODIC_ACCRUAL)
|
||||
List<Loan> findLoansForPeriodicAccrual(@Param("accountingType") Integer accountingType, @Param("tillDate") LocalDate tillDate,
|
||||
@Param("futureCharges") boolean futureCharges);
|
||||
|
||||
@Query(FIND_LOANS_FOR_ADD_ACCRUAL)
|
||||
List<Loan> findLoansForAddAccrual(@Param("accountingType") Integer accountingType, @Param("tillDate") LocalDate tillDate,
|
||||
@Param("futureCharges") boolean futureCharges);
|
||||
}
|
||||
|
||||
+8
@@ -263,4 +263,12 @@ public class LoanRepositoryWrapper {
|
||||
return repository.findLoanIdByStatusId(statusId);
|
||||
}
|
||||
|
||||
public List<Loan> findLoansForPeriodicAccrual(Integer accountingType, LocalDate tillDate, boolean futureCharges) {
|
||||
return repository.findLoansForPeriodicAccrual(accountingType, tillDate, futureCharges);
|
||||
}
|
||||
|
||||
public List<Loan> findLoansForAddAccrual(Integer accountingType, LocalDate tillDate, boolean futureCharges) {
|
||||
return repository.findLoansForAddAccrual(accountingType, tillDate, futureCharges);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+69
-91
@@ -252,26 +252,29 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
|
||||
public static LoanTransaction accrueInterest(final Office office, final Loan loan, final Money amount,
|
||||
final LocalDate interestAppliedDate, final ExternalId externalId) {
|
||||
BigDecimal principalPortion = null;
|
||||
BigDecimal feesPortion = null;
|
||||
BigDecimal penaltiesPortion = null;
|
||||
BigDecimal interestPortion = amount.getAmount();
|
||||
BigDecimal overPaymentPortion = null;
|
||||
boolean reversed = false;
|
||||
PaymentDetail paymentDetail = null;
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), interestAppliedDate, interestPortion,
|
||||
principalPortion, interestPortion, feesPortion, penaltiesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
|
||||
return accrueTransaction(loan, office, interestAppliedDate, interestPortion, interestPortion, null, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
|
||||
final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
|
||||
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
|
||||
externalId);
|
||||
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
|
||||
return applyCharge;
|
||||
}
|
||||
|
||||
public static LoanTransaction accrueTransaction(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||
final BigDecimal interestPortion, final BigDecimal feeChargesPortion, final BigDecimal penaltyChargesPortion,
|
||||
final ExternalId externalId) {
|
||||
BigDecimal principalPortion = null;
|
||||
BigDecimal overPaymentPortion = null;
|
||||
boolean reversed = false;
|
||||
PaymentDetail paymentDetail = null;
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, principalPortion, interestPortion,
|
||||
feeChargesPortion, penaltyChargesPortion, overPaymentPortion, reversed, paymentDetail, externalId);
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL.getValue(), dateOf, amount, null, interestPortion,
|
||||
feeChargesPortion, penaltyChargesPortion, null, false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction accrualAdjustment(final Loan loan, final Office office, final LocalDate dateOf, final BigDecimal amount,
|
||||
final BigDecimal interestPortion, final BigDecimal feePortion, final BigDecimal penaltyPortion, final ExternalId externalId) {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue(), dateOf, amount, null, interestPortion,
|
||||
feePortion, penaltyPortion, null, false, null, externalId);
|
||||
}
|
||||
|
||||
public static LoanTransaction initiateTransfer(final Office office, final Loan loan, final LocalDate transferDate,
|
||||
@@ -325,14 +328,6 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
return newTransaction;
|
||||
}
|
||||
|
||||
public static LoanTransaction accrueLoanCharge(final Loan loan, final Office office, final Money amount, final LocalDate applyDate,
|
||||
final Money feeCharges, final Money penaltyCharges, final ExternalId externalId) {
|
||||
final LoanTransaction applyCharge = new LoanTransaction(loan, office, LoanTransactionType.ACCRUAL, amount.getAmount(), applyDate,
|
||||
externalId);
|
||||
applyCharge.updateChargesComponents(feeCharges, penaltyCharges);
|
||||
return applyCharge;
|
||||
}
|
||||
|
||||
public static LoanTransaction creditBalanceRefund(final Loan loan, final Office office, final Money amount, final LocalDate paymentDate,
|
||||
final ExternalId externalId, PaymentDetail paymentDetail) {
|
||||
return new LoanTransaction(loan, office, LoanTransactionType.CREDIT_BALANCE_REFUND.getValue(), paymentDate, amount.getAmount(),
|
||||
@@ -492,47 +487,41 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
* penaltyCharges
|
||||
*/
|
||||
public void updateComponents(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
||||
final MonetaryCurrency currency = principal.getCurrency();
|
||||
this.principalPortion = defaultToNullIfZero(getPrincipalPortion(currency).plus(principal).getAmount());
|
||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
|
||||
this.principalPortion = MathUtil.zeroToNull(MathUtil.add(getPrincipalPortion(), MathUtil.toBigDecimal(principal)));
|
||||
this.interestPortion = MathUtil.zeroToNull(MathUtil.add(getInterestPortion(), MathUtil.toBigDecimal(interest)));
|
||||
updateChargesComponents(feeCharges, penaltyCharges);
|
||||
}
|
||||
|
||||
public void updateChargesComponents(final Money feeCharges, final Money penaltyCharges) {
|
||||
final MonetaryCurrency currency = feeCharges.getCurrency();
|
||||
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
|
||||
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
|
||||
this.feeChargesPortion = MathUtil.zeroToNull(MathUtil.add(getFeeChargesPortion(), MathUtil.toBigDecimal(feeCharges)));
|
||||
this.penaltyChargesPortion = MathUtil.zeroToNull(MathUtil.add(getPenaltyChargesPortion(), MathUtil.toBigDecimal(penaltyCharges)));
|
||||
}
|
||||
|
||||
private void updateChargesComponents(final Money feeCharges, final Money penaltyCharges, final Money unrecognizedCharges) {
|
||||
final MonetaryCurrency currency = feeCharges.getCurrency();
|
||||
this.feeChargesPortion = defaultToNullIfZero(getFeeChargesPortion(currency).plus(feeCharges).getAmount());
|
||||
this.penaltyChargesPortion = defaultToNullIfZero(getPenaltyChargesPortion(currency).plus(penaltyCharges).getAmount());
|
||||
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedCharges).getAmount());
|
||||
this.feeChargesPortion = MathUtil.zeroToNull(MathUtil.add(getFeeChargesPortion(), MathUtil.toBigDecimal(feeCharges)));
|
||||
this.penaltyChargesPortion = MathUtil.zeroToNull(MathUtil.add(getPenaltyChargesPortion(), MathUtil.toBigDecimal(penaltyCharges)));
|
||||
this.unrecognizedIncomePortion = MathUtil
|
||||
.zeroToNull(MathUtil.add(getUnrecognizedIncomePortion(), MathUtil.toBigDecimal(unrecognizedCharges)));
|
||||
}
|
||||
|
||||
private void updateInterestComponent(final Money interest, final Money unrecognizedInterest) {
|
||||
final MonetaryCurrency currency = interest.getCurrency();
|
||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).plus(interest).getAmount());
|
||||
this.unrecognizedIncomePortion = defaultToNullIfZero(getUnrecognizedIncomePortion(currency).plus(unrecognizedInterest).getAmount());
|
||||
this.interestPortion = MathUtil.zeroToNull(MathUtil.add(getInterestPortion(), MathUtil.toBigDecimal(interest)));
|
||||
this.unrecognizedIncomePortion = MathUtil
|
||||
.zeroToNull(MathUtil.add(getUnrecognizedIncomePortion(), MathUtil.toBigDecimal(unrecognizedInterest)));
|
||||
}
|
||||
|
||||
public void adjustInterestComponent(final MonetaryCurrency currency) {
|
||||
this.interestPortion = defaultToNullIfZero(getInterestPortion(currency).minus(getUnrecognizedIncomePortion(currency)).getAmount());
|
||||
public void adjustInterestComponent() {
|
||||
this.interestPortion = MathUtil.zeroToNull(MathUtil.subtract(getInterestPortion(), getUnrecognizedIncomePortion()));
|
||||
}
|
||||
|
||||
public void updateComponentsAndTotal(final Money principal, final Money interest, final Money feeCharges, final Money penaltyCharges) {
|
||||
updateComponents(principal, interest, feeCharges, penaltyCharges);
|
||||
|
||||
final MonetaryCurrency currency = principal.getCurrency();
|
||||
this.amount = getPrincipalPortion(currency).plus(getInterestPortion(currency)).plus(getFeeChargesPortion(currency))
|
||||
.plus(getPenaltyChargesPortion(currency)).getAmount();
|
||||
this.amount = MathUtil
|
||||
.nullToZero(MathUtil.add(getPrincipalPortion(), getInterestPortion(), getFeeChargesPortion(), getPenaltyChargesPortion()));
|
||||
}
|
||||
|
||||
public void setOverPayments(final Money overPayment) {
|
||||
if (overPayment != null) {
|
||||
this.overPaymentPortion = defaultToNullIfZero(overPayment.getAmount());
|
||||
}
|
||||
this.overPaymentPortion = MathUtil.zeroToNull(MathUtil.toBigDecimal(overPayment));
|
||||
}
|
||||
|
||||
public Money getPrincipalPortion(final MonetaryCurrency currency) {
|
||||
@@ -720,48 +709,20 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
return getTypeOf().isReAmortize() && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isAccrualActivity() {
|
||||
return getTypeOf().isAccrualActivity();
|
||||
}
|
||||
|
||||
public boolean isIdentifiedBy(final Long identifier) {
|
||||
return getId().equals(identifier);
|
||||
}
|
||||
|
||||
public boolean isBelongingToLoanOf(final Loan check) {
|
||||
return this.loan.getId().equals(check.getId());
|
||||
}
|
||||
|
||||
public boolean isNotBelongingToLoanOf(final Loan check) {
|
||||
return !isBelongingToLoanOf(check);
|
||||
}
|
||||
|
||||
public boolean isNonZero() {
|
||||
return this.amount.subtract(BigDecimal.ZERO).doubleValue() > 0;
|
||||
}
|
||||
|
||||
public boolean isGreaterThan(final Money monetaryAmount) {
|
||||
return getAmount(monetaryAmount.getCurrency()).isGreaterThan(monetaryAmount);
|
||||
return MathUtil.isGreaterThan(amount, MathUtil.toBigDecimal(monetaryAmount));
|
||||
}
|
||||
|
||||
public boolean isGreaterThanZero(final MonetaryCurrency currency) {
|
||||
return getAmount(currency).isGreaterThanZero();
|
||||
public boolean isGreaterThanZero() {
|
||||
return MathUtil.isGreaterThanZero(amount);
|
||||
}
|
||||
|
||||
public boolean isGreaterThanZeroAndLessThanOrEqualTo(BigDecimal totalOverpaid) {
|
||||
return isNonZero() && this.amount.compareTo(totalOverpaid) <= 0;
|
||||
return isGreaterThanZero() && !MathUtil.isGreaterThan(amount, totalOverpaid);
|
||||
}
|
||||
|
||||
public boolean isNotZero(final MonetaryCurrency currency) {
|
||||
return !getAmount(currency).isZero();
|
||||
}
|
||||
|
||||
private BigDecimal defaultToNullIfZero(final BigDecimal value) {
|
||||
BigDecimal result = value;
|
||||
if (BigDecimal.ZERO.compareTo(value) == 0) {
|
||||
result = null;
|
||||
}
|
||||
return result;
|
||||
public boolean isNotZero() {
|
||||
return !MathUtil.isEmpty(amount);
|
||||
}
|
||||
|
||||
public LoanTransactionData toData(final CurrencyData currencyData, final AccountTransferData transfer) {
|
||||
@@ -785,7 +746,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
thisTransactionData.put("id", getId());
|
||||
thisTransactionData.put("officeId", this.office.getId());
|
||||
thisTransactionData.put("type", transactionType);
|
||||
thisTransactionData.put("reversed", Boolean.valueOf(isReversed()));
|
||||
thisTransactionData.put("reversed", isReversed());
|
||||
thisTransactionData.put("date", getTransactionDate());
|
||||
thisTransactionData.put("currencyCode", currencyCode);
|
||||
thisTransactionData.put("amount", this.amount);
|
||||
@@ -871,16 +832,37 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
}
|
||||
|
||||
public boolean isAccrual() {
|
||||
return LoanTransactionType.ACCRUAL.equals(getTypeOf()) && isNotReversed();
|
||||
return getTypeOf().isAccrual();
|
||||
}
|
||||
|
||||
public boolean isAccrualAdjustment() {
|
||||
return getTypeOf().isAccrualAdjustment();
|
||||
}
|
||||
|
||||
public boolean isAccrualActivity() {
|
||||
return getTypeOf().isAccrualActivity();
|
||||
}
|
||||
|
||||
public boolean isAccrualRelated() {
|
||||
return isAccrual() || isAccrualAdjustment() || isAccrualActivity();
|
||||
}
|
||||
|
||||
public boolean isWaiveCharge() {
|
||||
return getTypeOf().isWaiveCharges();
|
||||
}
|
||||
|
||||
public boolean isWaiveInterest() {
|
||||
return getTypeOf().isWaiveInterest();
|
||||
}
|
||||
|
||||
public boolean isNonMonetaryTransaction() {
|
||||
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|
||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf()) || LoanTransactionType.ACCRUAL.equals(getTypeOf())
|
||||
|| LoanTransactionType.ACCRUAL_ACTIVITY.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.REAMORTIZE.equals(getTypeOf()) || LoanTransactionType.REAGE.equals(getTypeOf()));
|
||||
LoanTransactionType type = getTypeOf();
|
||||
return isNotReversed() && (type == LoanTransactionType.CONTRA || type == LoanTransactionType.MARKED_FOR_RESCHEDULING
|
||||
|| type == LoanTransactionType.ACCRUAL || type == LoanTransactionType.ACCRUAL_ADJUSTMENT
|
||||
|| type == LoanTransactionType.ACCRUAL_ACTIVITY || type == LoanTransactionType.APPROVE_TRANSFER
|
||||
|| type == LoanTransactionType.INITIATE_TRANSFER || type == LoanTransactionType.REJECT_TRANSFER
|
||||
|| type == LoanTransactionType.WITHDRAW_TRANSFER || type == LoanTransactionType.CHARGE_OFF
|
||||
|| type == LoanTransactionType.REAMORTIZE || type == LoanTransactionType.REAGE);
|
||||
}
|
||||
|
||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||
@@ -985,11 +967,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
}
|
||||
|
||||
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
||||
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement() || isRepayment() || isAccrualActivity();
|
||||
}
|
||||
|
||||
public boolean isAccrualTransaction() {
|
||||
return isAccrual();
|
||||
return isNotReversed() && (isDisbursement() || isAccrualActivity() || isRepaymentAtDisbursement() || isRepayment());
|
||||
}
|
||||
|
||||
public Money getOutstandingLoanBalanceMoney(final MonetaryCurrency currency) {
|
||||
|
||||
+14
-14
@@ -24,12 +24,12 @@ import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
|
||||
@Entity
|
||||
@Table(name = "m_loan_transaction_repayment_schedule_mapping")
|
||||
@@ -86,21 +86,13 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
||||
return (value == null || value.isZero()) ? null : value.getAmount();
|
||||
}
|
||||
|
||||
private BigDecimal defaultToZeroIfNull(final BigDecimal value) {
|
||||
BigDecimal result = value;
|
||||
if (value == null) {
|
||||
result = BigDecimal.ZERO;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public LoanRepaymentScheduleInstallment getLoanRepaymentScheduleInstallment() {
|
||||
return this.installment;
|
||||
}
|
||||
|
||||
public void updateComponents(@NotNull Money principal, @NotNull Money interest, @NotNull Money feeCharges,
|
||||
@NotNull Money penaltyCharges) {
|
||||
updateComponents(principal.getAmount(), interest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount());
|
||||
public void updateComponents(Money principal, Money interest, Money feeCharges, Money penaltyCharges) {
|
||||
updateComponents(MathUtil.toBigDecimal(principal), MathUtil.toBigDecimal(interest), MathUtil.toBigDecimal(feeCharges),
|
||||
MathUtil.toBigDecimal(penaltyCharges));
|
||||
}
|
||||
|
||||
void updateComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||
@@ -112,8 +104,7 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
||||
}
|
||||
|
||||
private void updateAmount() {
|
||||
this.amount = defaultToZeroIfNull(getPrincipalPortion()).add(defaultToZeroIfNull(getInterestPortion()))
|
||||
.add(defaultToZeroIfNull(getFeeChargesPortion())).add(defaultToZeroIfNull(getPenaltyChargesPortion()));
|
||||
this.amount = MathUtil.add(getPrincipalPortion(), getInterestPortion(), getFeeChargesPortion(), getPenaltyChargesPortion());
|
||||
}
|
||||
|
||||
public void setComponents(final BigDecimal principal, final BigDecimal interest, final BigDecimal feeCharges,
|
||||
@@ -146,6 +137,15 @@ public class LoanTransactionToRepaymentScheduleMapping extends AbstractPersistab
|
||||
return Money.of(currency, this.penaltyChargesPortion);
|
||||
}
|
||||
|
||||
public BigDecimal getPortion(AllocationType allocationType) {
|
||||
return switch (allocationType) {
|
||||
case PRINCIPAL -> getPrincipalPortion();
|
||||
case INTEREST -> getInterestPortion();
|
||||
case FEE -> getFeeChargesPortion();
|
||||
case PENALTY -> getPenaltyChargesPortion();
|
||||
};
|
||||
}
|
||||
|
||||
public LoanTransaction getLoanTransaction() {
|
||||
return loanTransaction;
|
||||
}
|
||||
|
||||
+5
@@ -66,6 +66,7 @@ public enum LoanTransactionType {
|
||||
INTEREST_PAYMENT_WAIVER(31, "loanTransactionType.interestPaymentWaiver"), //
|
||||
ACCRUAL_ACTIVITY(32, "loanTransactionType.accrualActivity"), //
|
||||
INTEREST_REFUND(33, "loanTransactionType.interestRefund"), //
|
||||
ACCRUAL_ADJUSTMENT(34, "loanTransactionType.accrualAdjustment"), //
|
||||
;
|
||||
|
||||
private final Integer value;
|
||||
@@ -115,6 +116,7 @@ public enum LoanTransactionType {
|
||||
case 31 -> LoanTransactionType.INTEREST_PAYMENT_WAIVER;
|
||||
case 32 -> LoanTransactionType.ACCRUAL_ACTIVITY;
|
||||
case 33 -> LoanTransactionType.INTEREST_REFUND;
|
||||
case 34 -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
|
||||
default -> LoanTransactionType.INVALID;
|
||||
};
|
||||
}
|
||||
@@ -228,4 +230,7 @@ public enum LoanTransactionType {
|
||||
return this.equals(LoanTransactionType.INTEREST_REFUND);
|
||||
}
|
||||
|
||||
public boolean isAccrualAdjustment() {
|
||||
return this == LoanTransactionType.ACCRUAL_ADJUSTMENT;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-9
@@ -45,14 +45,14 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
||||
totalInterest = totalInterest.plus(installment.getInterestCharged(currency));
|
||||
totalPrincipal = totalPrincipal.plus(installment.getPrincipal(currency));
|
||||
}
|
||||
LoanChargePaidBy accrualBy = null;
|
||||
List<LoanChargePaidBy> accruals = null;
|
||||
if (loanCharge.isSpecifiedDueDate()) {
|
||||
LoanRepaymentScheduleInstallment addedPeriod = addChargeOnlyRepaymentInstallmentIfRequired(loanCharge, installments);
|
||||
if (addedPeriod != null) {
|
||||
addedPeriod.updateObligationsMet(currency, disbursementDate);
|
||||
}
|
||||
accrualBy = loanCharge.getLoanChargePaidBySet().stream().filter(e -> e.getLoanTransaction().isAccrual()).findFirst()
|
||||
.orElse(null);
|
||||
accruals = loanCharge.getLoanChargePaidBySet().stream().filter(e -> !e.getLoanTransaction().isReversed()
|
||||
&& (e.getLoanTransaction().isAccrual() || e.getLoanTransaction().isAccrualAdjustment())).toList();
|
||||
}
|
||||
LocalDate startDate = disbursementDate;
|
||||
int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||
@@ -61,7 +61,8 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
||||
continue;
|
||||
}
|
||||
boolean installmentChargeApplicable = !installment.isRecalculatedInterestComponent();
|
||||
boolean isFirstPeriod = installment.getInstallmentNumber().equals(firstNormalInstallmentNumber);
|
||||
Integer installmentNumber = installment.getInstallmentNumber();
|
||||
boolean isFirstPeriod = installmentNumber.equals(firstNormalInstallmentNumber);
|
||||
Predicate<LoanCharge> feePredicate = e -> e.isFeeCharge() && !e.isDueAtDisbursement();
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
final Money feeChargesDue = calcChargeDue(startDate, dueDate, loanCharge, currency, installment, totalPrincipal, totalInterest,
|
||||
@@ -82,13 +83,19 @@ public class SingleLoanChargeRepaymentScheduleProcessingWrapper {
|
||||
installment.addToChargePortion(feeChargesDue, feeChargesWaived, feeChargesWrittenOff, penaltyChargesDue, penaltyChargesWaived,
|
||||
penaltyChargesWrittenOff);
|
||||
|
||||
if (accrualBy != null && installment.isAdditional() && loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) {
|
||||
Money amount = Money.of(currency, accrualBy.getAmount());
|
||||
if (accruals != null && !accruals.isEmpty() && installment.isAdditional()
|
||||
&& loanCharge.isDueInPeriod(startDate, dueDate, isFirstPeriod)) {
|
||||
BigDecimal amount = null;
|
||||
for (LoanChargePaidBy accrual : accruals) {
|
||||
accrual.setInstallmentNumber(installmentNumber);
|
||||
amount = accrual.getLoanTransaction().isAccrual() ? MathUtil.add(amount, accrual.getAmount())
|
||||
: MathUtil.subtract(amount, accrual.getAmount());
|
||||
}
|
||||
Money accruedAmount = Money.of(currency, MathUtil.negativeToZero(amount));
|
||||
boolean isFee = loanCharge.isFeeCharge();
|
||||
installment.updateAccrualPortion(installment.getInterestAccrued(currency),
|
||||
MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? amount : null)),
|
||||
MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : amount)));
|
||||
accrualBy.setInstallmentNumber(installment.getInstallmentNumber());
|
||||
MathUtil.plus(installment.getFeeAccrued(currency), (isFee ? accruedAmount : null)),
|
||||
MathUtil.plus(installment.getPenaltyAccrued(currency), (isFee ? null : accruedAmount)));
|
||||
}
|
||||
startDate = dueDate;
|
||||
}
|
||||
|
||||
+2
-2
@@ -166,7 +166,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
// pass through for new transactions
|
||||
if (loanTransaction.getId() == null) {
|
||||
processLatestTransaction(loanTransaction, new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||
loanTransaction.adjustInterestComponent(currency);
|
||||
loanTransaction.adjustInterestComponent();
|
||||
} else {
|
||||
/**
|
||||
* For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties)
|
||||
@@ -178,7 +178,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
// re-process transaction
|
||||
processLatestTransaction(newLoanTransaction,
|
||||
new TransactionCtx(currency, installments, charges, overpaymentHolder, null));
|
||||
newLoanTransaction.adjustInterestComponent(currency);
|
||||
newLoanTransaction.adjustInterestComponent();
|
||||
/**
|
||||
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
||||
* changedTransactionDetail accordingly
|
||||
|
||||
+39
@@ -18,6 +18,10 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isAfterPeriod;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper.isBeforePeriod;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
@@ -2805,4 +2809,39 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
||||
.feeCharges(feeCharges) //
|
||||
.penaltyCharges(penaltyCharges);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate targetDate) {
|
||||
if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) {
|
||||
return null;
|
||||
}
|
||||
if (isBeforePeriod(targetDate, installment, false)) {
|
||||
return null;
|
||||
}
|
||||
Loan loan = installment.getLoan();
|
||||
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
||||
BigDecimal interest = installment.getInterestCharged();
|
||||
Money zero = Money.zero(currency);
|
||||
if (MathUtil.isEmpty(interest)) {
|
||||
return zero;
|
||||
}
|
||||
if (isAfterPeriod(targetDate, installment) || DateUtils.isEqual(targetDate, installment.getDueDate())) {
|
||||
return installment.getInterestCharged(currency);
|
||||
}
|
||||
|
||||
BigDecimal interestPortion;
|
||||
LocalDate startDate = installment.getFromDate();
|
||||
LocalDate dueDate = installment.getDueDate();
|
||||
if (DateUtils.isBefore(startDate, loan.getInterestChargedFromDate())) {
|
||||
startDate = loan.getInterestChargedFromDate();
|
||||
}
|
||||
if (!DateUtils.isBefore(startDate, dueDate) || !DateUtils.isBefore(startDate, targetDate)) {
|
||||
return zero;
|
||||
}
|
||||
int totalNumberOfDays = DateUtils.getExactDifferenceInDays(startDate, dueDate);
|
||||
int daysToBeAccrued = DateUtils.getExactDifferenceInDays(startDate, targetDate);
|
||||
double interestPerDay = interest.doubleValue() / totalNumberOfDays;
|
||||
interestPortion = BigDecimal.valueOf(interestPerDay * daysToBeAccrued);
|
||||
return Money.of(currency, interestPortion);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -18,14 +18,17 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Set;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO;
|
||||
|
||||
@@ -42,4 +45,5 @@ public interface LoanScheduleGenerator {
|
||||
MathContext mc, Loan loan, HolidayDetailDTO holidayDetailDTO,
|
||||
LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor);
|
||||
|
||||
Money getPeriodInterestTillDate(@NotNull LoanRepaymentScheduleInstallment installment, @NotNull LocalDate targetDate);
|
||||
}
|
||||
|
||||
+15
-12
@@ -18,30 +18,33 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.exception.MultiException;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleAccrualData;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
|
||||
public interface LoanAccrualsProcessingService {
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate) throws MultiException;
|
||||
void addPeriodicAccruals(@NotNull LocalDate tilldate) throws MultiException;
|
||||
|
||||
void addPeriodicAccruals(LocalDate tilldate, Loan loan) throws MultiException;
|
||||
void addPeriodicAccruals(@NotNull LocalDate tilldate, @NotNull Loan loan) throws MultiException;
|
||||
|
||||
void addAccrualAccounting(Long loanId, Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas) throws Exception;
|
||||
void addAccruals(@NotNull LocalDate tilldate) throws MultiException;
|
||||
|
||||
void reprocessExistingAccruals(@NotNull Loan loan);
|
||||
|
||||
void processAccrualsForInterestRecalculation(@NotNull Loan loan, boolean isInterestRecalculationEnabled);
|
||||
|
||||
void processIncomePostingAndAccruals(@NotNull Loan loan);
|
||||
|
||||
void addIncomeAndAccrualTransactions(Long loanId) throws Exception;
|
||||
|
||||
void reprocessExistingAccruals(Loan loan);
|
||||
void processAccrualsOnLoanClosure(@NotNull Loan loan);
|
||||
|
||||
void processAccrualsForInterestRecalculation(Loan loan, boolean isInterestRecalculationEnabled);
|
||||
void processAccrualsOnLoanReopen(@NotNull Loan loan);
|
||||
|
||||
void processIncomePostingAndAccruals(Loan loan);
|
||||
|
||||
void processAccrualsForLoanClosure(Loan loan);
|
||||
|
||||
void processAccrualsForLoanForeClosure(Loan loan, LocalDate foreClosureDate, Collection<LoanTransaction> newAccrualTransactions);
|
||||
void processAccrualsOnLoanForeClosure(@NotNull Loan loan, @NotNull LocalDate foreClosureDate,
|
||||
@NotNull List<LoanTransaction> newAccrualTransactions);
|
||||
}
|
||||
|
||||
+50
@@ -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.service;
|
||||
|
||||
import java.util.Map;
|
||||
import org.apache.fineract.infrastructure.codes.domain.CodeValue;
|
||||
import org.apache.fineract.infrastructure.core.api.JsonCommand;
|
||||
import org.apache.fineract.organisation.staff.domain.Staff;
|
||||
import org.apache.fineract.portfolio.fund.domain.Fund;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.useradministration.domain.AppUser;
|
||||
|
||||
public interface LoanAssembler {
|
||||
|
||||
Loan assembleFrom(Long accountId);
|
||||
|
||||
Loan assembleFrom(JsonCommand command);
|
||||
|
||||
void setHelpers(Loan loanAccount);
|
||||
|
||||
void accountNumberGeneration(JsonCommand command, Loan loan);
|
||||
|
||||
CodeValue findCodeValueByIdIfProvided(Long codeValueId);
|
||||
|
||||
Fund findFundByIdIfProvided(Long fundId);
|
||||
|
||||
Staff findLoanOfficerByIdIfProvided(Long loanOfficerId);
|
||||
|
||||
Map<String, Object> updateFrom(JsonCommand command, Loan loan);
|
||||
|
||||
Map<String, Object> updateLoanApplicationAttributesForWithdrawal(Loan loan, JsonCommand command, AppUser currentUser);
|
||||
|
||||
Map<String, Object> updateLoanApplicationAttributesForRejection(Loan loan, JsonCommand command, AppUser currentUser);
|
||||
}
|
||||
+2
-3
@@ -19,6 +19,7 @@
|
||||
package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
import org.apache.fineract.portfolio.charge.data.ChargeData;
|
||||
import org.apache.fineract.portfolio.charge.domain.Charge;
|
||||
@@ -38,12 +39,10 @@ public interface LoanChargeReadPlatformService {
|
||||
|
||||
Collection<LoanChargeData> retrieveLoanChargesForFeePayment(Integer paymentMode, Integer loanStatus);
|
||||
|
||||
Collection<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
|
||||
List<LoanInstallmentChargeData> retrieveInstallmentLoanCharges(Long loanChargeId, boolean onlyPaymentPendingCharges);
|
||||
|
||||
Collection<Integer> retrieveOverdueInstallmentChargeFrequencyNumber(Loan loan, Charge charge, Integer periodNumber);
|
||||
|
||||
Collection<LoanChargeData> retrieveLoanChargesForAccrual(Long loanId);
|
||||
|
||||
Collection<LoanChargePaidByData> retrieveLoanChargesPaidBy(Long chargeId, LoanTransactionType transactionType,
|
||||
Integer installmentNumber);
|
||||
|
||||
|
||||
+2
@@ -323,6 +323,8 @@ public final class LoanEnumerations {
|
||||
LoanTransactionType.ACCRUAL_ACTIVITY.getCode(), "Accrual Activity");
|
||||
case INTEREST_REFUND -> new LoanTransactionEnumData(LoanTransactionType.INTEREST_REFUND.getValue().longValue(),
|
||||
LoanTransactionType.INTEREST_REFUND.getCode(), "Interest Refund");
|
||||
case ACCRUAL_ADJUSTMENT -> new LoanTransactionEnumData(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue().longValue(),
|
||||
LoanTransactionType.ACCRUAL_ADJUSTMENT.getCode(), "Accrual Adjustment");
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -46,4 +46,5 @@
|
||||
<include relativeToChangelogFile="true" file="parts/1021_add_loan_status_change_history.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1022_add_interest_refund_support.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1023_add_charge_off_behaviour.xml"/>
|
||||
<include relativeToChangelogFile="true" file="parts/1024_accrual_adjustment_transaction_type.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?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.3.xsd">
|
||||
|
||||
<changeSet author="fineract" id="1">
|
||||
<insert tableName="m_external_event_configuration">
|
||||
<column name="type" value="LoanAccrualAdjustmentTransactionBusinessEvent"/>
|
||||
<column name="enabled" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
<changeSet author="fineract" id="2">
|
||||
<insert tableName="r_enum_value">
|
||||
<column name="enum_name" value="transaction_type_enum"/>
|
||||
<column name="enum_id" valueNumeric="34"/>
|
||||
<column name="enum_message_property" value="Accrual Adjustment"/>
|
||||
<column name="enum_value" value="Accrual Adjustment"/>
|
||||
<column name="enum_type" valueBoolean="false"/>
|
||||
</insert>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
Reference in New Issue
Block a user