FINERACT-2181: fix undo transaction on charges waiver not reversing changes on loan charge
This commit is contained in:
+23
-1
@@ -117,9 +117,11 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
@Column(name = "is_penalty", nullable = false)
|
||||
private boolean penaltyCharge = false;
|
||||
|
||||
@Setter
|
||||
@Column(name = "is_paid_derived", nullable = false)
|
||||
private boolean paid = false;
|
||||
|
||||
@Setter
|
||||
@Column(name = "waived", nullable = false)
|
||||
private boolean waived = false;
|
||||
|
||||
@@ -290,7 +292,8 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
public Money waive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) {
|
||||
if (isInstalmentFee()) {
|
||||
final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber);
|
||||
final Money amountWaived = chargePerInstallment.waive(currency);
|
||||
chargePerInstallment.waive();
|
||||
final Money amountWaived = chargePerInstallment.getAmountWaived(currency);
|
||||
if (this.amountWaived == null) {
|
||||
this.amountWaived = BigDecimal.ZERO;
|
||||
}
|
||||
@@ -310,6 +313,25 @@ public class LoanCharge extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
}
|
||||
|
||||
public void undoWaive(final MonetaryCurrency currency, final Integer loanInstallmentNumber) {
|
||||
if (isInstalmentFee()) {
|
||||
final LoanInstallmentCharge chargePerInstallment = getInstallmentLoanCharge(loanInstallmentNumber);
|
||||
chargePerInstallment.undoWaive();
|
||||
Money amountReversed = chargePerInstallment.getAmountOutstanding(currency);
|
||||
this.amountWaived = this.amountWaived.subtract(amountReversed.getAmount());
|
||||
this.amountOutstanding = this.amountOutstanding.add(amountReversed.getAmount());
|
||||
if (!determineIfFullyPaid()) {
|
||||
this.paid = false;
|
||||
this.waived = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.amountOutstanding = this.amountWaived;
|
||||
this.amountWaived = BigDecimal.ZERO;
|
||||
this.paid = false;
|
||||
this.waived = false;
|
||||
}
|
||||
|
||||
private BigDecimal calculateAmountOutstanding(final MonetaryCurrency currency) {
|
||||
return getAmount(currency).minus(getAmountWaived(currency)).minus(getAmountPaid(currency)).getAmount();
|
||||
}
|
||||
|
||||
+14
-2
@@ -65,9 +65,11 @@ public class LoanInstallmentCharge extends AbstractPersistableCustom<Long> imple
|
||||
@Column(name = "amount_through_charge_payment", scale = 6, precision = 19, nullable = true)
|
||||
private BigDecimal amountThroughChargePayment;
|
||||
|
||||
@Setter
|
||||
@Column(name = "is_paid_derived", nullable = false)
|
||||
private boolean paid = false;
|
||||
|
||||
@Setter
|
||||
@Column(name = "waived", nullable = false)
|
||||
private boolean waived = false;
|
||||
|
||||
@@ -97,18 +99,28 @@ public class LoanInstallmentCharge extends AbstractPersistableCustom<Long> imple
|
||||
this.paid = determineIfFullyPaid();
|
||||
}
|
||||
|
||||
public Money waive(final MonetaryCurrency currency) {
|
||||
public void waive() {
|
||||
this.amountWaived = this.amountOutstanding;
|
||||
this.amountOutstanding = BigDecimal.ZERO;
|
||||
this.paid = false;
|
||||
this.waived = true;
|
||||
return getAmountWaived(currency);
|
||||
}
|
||||
|
||||
public void undoWaive() {
|
||||
this.amountOutstanding = this.amountWaived;
|
||||
this.amountWaived = BigDecimal.ZERO;
|
||||
this.paid = false;
|
||||
this.waived = false;
|
||||
}
|
||||
|
||||
public Money getAmountWaived(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.amountWaived);
|
||||
}
|
||||
|
||||
public Money getAmountOutstanding(final MonetaryCurrency currency) {
|
||||
return Money.of(currency, this.amountOutstanding);
|
||||
}
|
||||
|
||||
private boolean determineIfFullyPaid() {
|
||||
if (this.amount == null) {
|
||||
return true;
|
||||
|
||||
+12
@@ -36,6 +36,7 @@ import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.account.PortfolioAccountType;
|
||||
import org.apache.fineract.portfolio.account.service.AccountTransfersWritePlatformService;
|
||||
@@ -44,6 +45,7 @@ import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallmentRepository;
|
||||
@@ -119,6 +121,16 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService {
|
||||
newTransactionDetail = LoanTransaction.waiver(loan.getOffice(), loan, transactionAmountAsMoney, transactionDate,
|
||||
interestComponent, unrecognizedIncome, txnExternalId);
|
||||
}
|
||||
if (transactionToAdjust.isChargesWaiver()) {
|
||||
transactionToAdjust.getLoanChargesPaid().forEach(loanChargePaidBy -> {
|
||||
LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
|
||||
MonetaryCurrency currency = loanCharge.getLoan().getCurrency();
|
||||
|
||||
Integer installmentNumber = loanChargePaidBy.getInstallmentNumber();
|
||||
|
||||
loanCharge.undoWaive(currency, installmentNumber);
|
||||
});
|
||||
}
|
||||
|
||||
LocalDate recalculateFrom = null;
|
||||
|
||||
|
||||
+53
@@ -19,14 +19,19 @@
|
||||
package org.apache.fineract.integrationtests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.google.common.collect.Streams;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Stream;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdLoanChargePaidByData;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.PostChargesResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
@@ -35,9 +40,11 @@ import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||
import org.junit.jupiter.api.Named;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
@@ -266,4 +273,50 @@ public class LoanWaiveChargeTest extends BaseLoanIntegrationTest {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanCannotBeChargedOffWhenUndoingFeeWaiver() {
|
||||
double amount = 1000.0;
|
||||
AtomicLong appliedLoanId = new AtomicLong();
|
||||
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
// Create Loan Product
|
||||
PostLoanProductsRequest product = create4IProgressive();
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "01 January 2023", amount, 9.9, 4, null);
|
||||
appliedLoanId.set(loanId);
|
||||
|
||||
// disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 January 2023");
|
||||
});
|
||||
runAt("23 January 2023", () -> {
|
||||
// create charge
|
||||
double chargeAmount = 5.0;
|
||||
PostChargesResponse chargeResult = createCharge(chargeAmount, "EUR");
|
||||
Long chargeId = chargeResult.getResourceId();
|
||||
|
||||
PostLoansLoanIdChargesResponse loanChargeResult = addLoanCharge(appliedLoanId.get(), chargeId, "23 January 2023", chargeAmount);
|
||||
long loanChargeId = loanChargeResult.getResourceId();
|
||||
|
||||
// waive charge
|
||||
waiveLoanCharge(appliedLoanId.get(), loanChargeId, 1);
|
||||
|
||||
GetLoansLoanIdTransactionsResponse loanTransactions = loanTransactionHelper.getLoanTransactions(appliedLoanId.get());
|
||||
Optional<GetLoansLoanIdLoanChargePaidByData> chargeData = loanTransactions.getContent().stream()
|
||||
.flatMap(t -> t.getLoanChargePaidByList().stream()).filter(t -> Objects.equals(loanChargeId, t.getChargeId()))
|
||||
.findAny();
|
||||
|
||||
loanTransactionHelper.reverseLoanTransaction(appliedLoanId.get(), chargeData.get().getTransactionId(), "23 January 2023");
|
||||
CallFailedRuntimeException callFailedRuntimeException = assertThrows(CallFailedRuntimeException.class,
|
||||
() -> chargeOffLoan(appliedLoanId.get(), "05 January 2023"));
|
||||
assertTrue(callFailedRuntimeException.getMessage().contains("error.msg.loan.monetary.transactions.after.charge.off"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user