FINERACT-2042 Credit Allocation for Principal and Interest components
This commit is contained in:
committed by
Peter Bagrij
parent
717eb69425
commit
c504996c1d
+3
-1
@@ -62,7 +62,9 @@ public class LoanTransactionRelation extends AbstractAuditableWithUTCDateTimeCus
|
||||
|
||||
public static LoanTransactionRelation linkToTransaction(@NotNull LoanTransaction fromTransaction,
|
||||
@NotNull LoanTransaction toTransaction, LoanTransactionRelationTypeEnum relation) {
|
||||
return new LoanTransactionRelation(fromTransaction, toTransaction, null, relation);
|
||||
LoanTransactionRelation loanTransactionRelation = new LoanTransactionRelation(fromTransaction, toTransaction, null, relation);
|
||||
fromTransaction.getLoanTransactionRelations().add(loanTransactionRelation);
|
||||
return loanTransactionRelation;
|
||||
}
|
||||
|
||||
public static LoanTransactionRelation linkToCharge(@NotNull LoanTransaction fromTransaction, @NotNull LoanCharge loanCharge,
|
||||
|
||||
+1
-1
@@ -474,7 +474,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
}
|
||||
|
||||
private void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,
|
||||
protected void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
|
||||
+189
@@ -20,16 +20,23 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
|
||||
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
@@ -41,22 +48,28 @@ import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
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.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.DueType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
@@ -172,6 +185,182 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasNoCustomCreditAllocationRule(LoanTransaction loanTransaction) {
|
||||
return (loanTransaction.getLoan().getCreditAllocationRules() == null || !loanTransaction.getLoan().getCreditAllocationRules()
|
||||
.stream().anyMatch(e -> e.getTransactionType().getLoanTransactionType().equals(loanTransaction.getTypeOf())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void processCreditTransaction(LoanTransaction loanTransaction, MoneyHolder overpaymentHolder, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
if (hasNoCustomCreditAllocationRule(loanTransaction)) {
|
||||
super.processCreditTransaction(loanTransaction, overpaymentHolder, currency, installments);
|
||||
} else {
|
||||
log.debug("Processing credit transaction with custom credit allocation rules");
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
installments.sort(byDate);
|
||||
final Money zeroMoney = Money.zero(currency);
|
||||
Money transactionAmount = loanTransaction.getAmount(currency);
|
||||
Money amountToDistribute = MathUtil
|
||||
.negativeToZero(loanTransaction.getAmount(currency).minus(overpaymentHolder.getMoneyObject()));
|
||||
Money repaidAmount = MathUtil.negativeToZero(transactionAmount.minus(amountToDistribute));
|
||||
loanTransaction.setOverPayments(repaidAmount);
|
||||
overpaymentHolder.setMoneyObject(overpaymentHolder.getMoneyObject().minus(repaidAmount));
|
||||
|
||||
if (amountToDistribute.isGreaterThanZero()) {
|
||||
if (loanTransaction.isChargeback()) {
|
||||
Optional<LoanTransaction> originalTransaction = loanTransaction.getLoan().getLoanTransactions(
|
||||
tr -> tr.getLoanTransactionRelations().stream().anyMatch(this.hasMatchingToLoanTransaction(loanTransaction)))
|
||||
.stream().findFirst();
|
||||
if (originalTransaction.isEmpty()) {
|
||||
throw new RuntimeException("Chargeback transaction must have an original transaction");
|
||||
}
|
||||
|
||||
Map<AllocationType, BigDecimal> originalAllocation = getOriginalAllocation(originalTransaction.get());
|
||||
LoanCreditAllocationRule chargeBackAllocationRule = getChargebackAllocationRules(loanTransaction);
|
||||
Map<AllocationType, Money> chargebackAllocation = calculateChargebackAllocationMap(originalAllocation,
|
||||
amountToDistribute.getAmount(), chargeBackAllocationRule.getAllocationTypes(), currency);
|
||||
|
||||
loanTransaction.updateComponents(chargebackAllocation.get(PRINCIPAL), chargebackAllocation.get(INTEREST),
|
||||
chargebackAllocation.get(FEE), chargebackAllocation.get(PENALTY));
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
boolean loanTransactionMapped = false;
|
||||
LocalDate pastDueDate = null;
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
pastDueDate = currentInstallment.getDueDate();
|
||||
if (!currentInstallment.isAdditional() && DateUtils.isAfter(currentInstallment.getDueDate(), transactionDate)) {
|
||||
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(currency);
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
|
||||
// If already exists an additional installment just update the due date and
|
||||
// principal from the Loan chargeback / CBR transaction
|
||||
} else if (currentInstallment.isAdditional()) {
|
||||
if (DateUtils.isAfter(transactionDate, currentInstallment.getDueDate())) {
|
||||
currentInstallment.updateDueDate(transactionDate);
|
||||
}
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(currency);
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
loanTransactionMapped = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// New installment will be added (N+1 scenario)
|
||||
if (!loanTransactionMapped) {
|
||||
if (loanTransaction.getTransactionDate().equals(pastDueDate)) {
|
||||
LoanRepaymentScheduleInstallment currentInstallment = installments.get(installments.size() - 1);
|
||||
currentInstallment.addToCredits(transactionAmount.getAmount());
|
||||
currentInstallment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = currentInstallment.getInterestCharged(currency);
|
||||
currentInstallment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
currentInstallment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
} else {
|
||||
Loan loan = loanTransaction.getLoan();
|
||||
LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(loan,
|
||||
(installments.size() + 1), pastDueDate, transactionDate, zeroMoney.getAmount(), zeroMoney.getAmount(),
|
||||
zeroMoney.getAmount(), zeroMoney.getAmount(), false, null);
|
||||
installment.markAsAdditional();
|
||||
installment.addToCredits(transactionAmount.getAmount());
|
||||
installment.addToPrincipal(transactionDate, chargebackAllocation.get(PRINCIPAL));
|
||||
Money originalInterest = installment.getInterestCharged(currency);
|
||||
installment.updateInterestCharged(
|
||||
originalInterest.plus(chargebackAllocation.get(INTEREST)).getAmountDefaultedToNullIfZero());
|
||||
loan.addLoanRepaymentScheduleInstallment(installment);
|
||||
if (repaidAmount.isGreaterThanZero()) {
|
||||
installment.payPrincipalComponent(loanTransaction.getTransactionDate(), repaidAmount);
|
||||
transactionMappings.add(LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction, installment,
|
||||
repaidAmount, zeroMoney, zeroMoney, zeroMoney));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LoanCreditAllocationRule getChargebackAllocationRules(LoanTransaction loanTransaction) {
|
||||
LoanCreditAllocationRule chargeBackAllocationRule = loanTransaction.getLoan().getCreditAllocationRules().stream()
|
||||
.filter(tr -> tr.getTransactionType().equals(CreditAllocationTransactionType.CHARGEBACK)).findFirst().orElseThrow();
|
||||
return chargeBackAllocationRule;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<AllocationType, BigDecimal> getOriginalAllocation(LoanTransaction originalLoanTransaction) {
|
||||
Map<AllocationType, BigDecimal> originalAllocation = new HashMap<>();
|
||||
originalAllocation.put(PRINCIPAL, originalLoanTransaction.getPrincipalPortion());
|
||||
originalAllocation.put(INTEREST, originalLoanTransaction.getInterestPortion());
|
||||
originalAllocation.put(PENALTY, originalLoanTransaction.getPenaltyChargesPortion());
|
||||
originalAllocation.put(FEE, originalLoanTransaction.getFeeChargesPortion());
|
||||
return originalAllocation;
|
||||
}
|
||||
|
||||
protected Map<AllocationType, Money> calculateChargebackAllocationMap(Map<AllocationType, BigDecimal> originalAllocation,
|
||||
BigDecimal amountToDistribute, List<AllocationType> allocationTypes, MonetaryCurrency currency) {
|
||||
BigDecimal remainingAmount = amountToDistribute;
|
||||
Map<AllocationType, Money> result = new HashMap<>();
|
||||
Arrays.stream(AllocationType.values()).forEach(allocationType -> result.put(allocationType, Money.of(currency, BigDecimal.ZERO)));
|
||||
for (AllocationType allocationType : allocationTypes) {
|
||||
if (remainingAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal originalAmount = originalAllocation.get(allocationType);
|
||||
if (originalAmount != null && remainingAmount.compareTo(originalAmount) > 0
|
||||
&& originalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
result.put(allocationType, Money.of(currency, originalAmount));
|
||||
remainingAmount = remainingAmount.subtract(originalAmount);
|
||||
} else if (originalAmount != null && remainingAmount.compareTo(originalAmount) <= 0
|
||||
&& originalAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
result.put(allocationType, Money.of(currency, remainingAmount));
|
||||
remainingAmount = BigDecimal.ZERO;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Predicate<LoanTransactionRelation> hasMatchingToLoanTransaction(LoanTransaction loanTransaction) {
|
||||
return relation -> {
|
||||
if (loanTransaction.getId() != null && relation.getToTransaction().getId() != null) {
|
||||
return Objects.equals(relation.getToTransaction().getId(), loanTransaction.getId());
|
||||
} else {
|
||||
return relation.getToTransaction().getTypeOf().equals(loanTransaction.getTypeOf())
|
||||
&& relation.getToTransaction().getAmount().compareTo(loanTransaction.getAmount()) == 0
|
||||
&& relation.getToTransaction().isReversed() == loanTransaction.isReversed()
|
||||
&& relation.getToTransaction().getTransactionDate().compareTo(loanTransaction.getTransactionDate()) == 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||
|
||||
+3
-2
@@ -430,11 +430,12 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
final BigDecimal overpaidAmount = Objects.isNull(loanTransactionDTO.getOverPayment()) ? BigDecimal.ZERO
|
||||
: loanTransactionDTO.getOverPayment();
|
||||
|
||||
if (BigDecimal.ZERO.compareTo(overpaidAmount) == 0) {
|
||||
if (BigDecimal.ZERO.compareTo(overpaidAmount) == 0) { // when no overpay
|
||||
helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.LOAN_PORTFOLIO.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
} else if (overpaidAmount.compareTo(amount) >= 0) {
|
||||
} else if (overpaidAmount.compareTo(amount) >= 0) { // when the overpay amount is matching with the normal
|
||||
// amount
|
||||
helper.createJournalEntriesAndReversalsForLoan(office, currencyCode, AccrualAccountsForLoan.OVERPAYMENT.getValue(),
|
||||
AccrualAccountsForLoan.FUND_SOURCE.getValue(), loanProductId, paymentTypeId, loanId, transactionId, transactionDate,
|
||||
amount, isReversal);
|
||||
|
||||
+7
@@ -66,6 +66,7 @@ import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCollateralManagement;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
@@ -390,6 +391,12 @@ public class LoanAssembler {
|
||||
r.getFutureInstallmentAllocationRule()))
|
||||
.toList();
|
||||
loanApplication.setPaymentAllocationRules(loanPaymentAllocationRules);
|
||||
|
||||
if (loanProduct.getCreditAllocationRules() != null && loanProduct.getCreditAllocationRules().size() > 0) {
|
||||
List<LoanCreditAllocationRule> loanCreditAllocationRules = loanProduct.getCreditAllocationRules().stream()
|
||||
.map(r -> new LoanCreditAllocationRule(loanApplication, r.getTransactionType(), r.getAllocationTypes())).toList();
|
||||
loanApplication.setCreditAllocationRules(loanCreditAllocationRules);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+235
-14
@@ -18,13 +18,25 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CHARGEBACK;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.REPAYMENT;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.refEq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -39,21 +51,27 @@ import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.AllocationType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
@@ -95,13 +113,13 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
final BigDecimal chargeAmount = BigDecimal.valueOf(100);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = LocalDate.of(2023, 1, 5);
|
||||
LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
Loan loan = mock(Loan.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
LoanRepaymentScheduleInstallment installment = Mockito
|
||||
.spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, disbursementDate.plusMonths(1), BigDecimal.valueOf(0L),
|
||||
@@ -138,13 +156,13 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = LocalDate.of(2023, 1, 5);
|
||||
LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
Loan loan = mock(Loan.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(20.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
@@ -184,18 +202,18 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
BigDecimal chargeAmount = BigDecimal.valueOf(100.00);
|
||||
LocalDate disbursementDate = LocalDate.of(2023, 1, 1);
|
||||
LocalDate transactionDate = disbursementDate.plusMonths(1);
|
||||
LoanTransaction loanTransaction = Mockito.mock(LoanTransaction.class);
|
||||
LoanTransaction loanTransaction = mock(LoanTransaction.class);
|
||||
MonetaryCurrency currency = MONETARY_CURRENCY;
|
||||
LoanCharge charge = Mockito.mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = Mockito.mock(LoanChargePaidBy.class);
|
||||
LoanCharge charge = mock(LoanCharge.class);
|
||||
LoanChargePaidBy chargePaidBy = mock(LoanChargePaidBy.class);
|
||||
Money overpaidAmount = Money.zero(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
Loan loan = Mockito.mock(Loan.class);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanProductRelatedDetail loanProductRelatedDetail = mock(LoanProductRelatedDetail.class);
|
||||
Money chargeAmountMoney = Money.of(currency, chargeAmount);
|
||||
BigDecimal transactionAmount = BigDecimal.valueOf(120.00);
|
||||
Money transactionAmountMoney = Money.of(currency, transactionAmount);
|
||||
LoanPaymentAllocationRule loanPaymentAllocationRule = Mockito.mock(LoanPaymentAllocationRule.class);
|
||||
LoanPaymentAllocationRule loanPaymentAllocationRule = mock(LoanPaymentAllocationRule.class);
|
||||
LoanRepaymentScheduleInstallment installment = Mockito
|
||||
.spy(new LoanRepaymentScheduleInstallment(loan, 1, disbursementDate, transactionDate, BigDecimal.valueOf(100L),
|
||||
BigDecimal.valueOf(0L), chargeAmount, BigDecimal.valueOf(0L), false, null, BigDecimal.ZERO));
|
||||
@@ -232,4 +250,207 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
assertEquals(0, BigDecimal.valueOf(80).compareTo(installment.getPrincipalOutstanding(currency).getAmount()));
|
||||
Mockito.verify(loan, Mockito.times(1)).getPaymentAllocationRules();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRuleInterestAndPrincipal() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(INTEREST, PRINCIPAL, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan);
|
||||
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
|
||||
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
underTest.processCreditTransaction(chargeBackTransaction, overpaymentHolder, MONETARY_CURRENCY, installments);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("20.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(5.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(20.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterest() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan);
|
||||
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
|
||||
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment = createMockInstallment(LocalDate.of(2023, 1, 31), false);
|
||||
installments.add(installment);
|
||||
|
||||
// when
|
||||
underTest.processCreditTransaction(chargeBackTransaction, overpaymentHolder, MONETARY_CURRENCY, installments);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProcessCreditTransactionWithAllocationRulePrincipalAndInterestWithAdditionalInstallment() {
|
||||
// given
|
||||
Loan loan = mock(Loan.class);
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = createMockCreditAllocationRule(PRINCIPAL, INTEREST, PENALTY, FEE);
|
||||
Mockito.when(loan.getCreditAllocationRules()).thenReturn(List.of(mockCreditAllocationRule));
|
||||
LoanTransaction repayment = createRepayment(loan);
|
||||
lenient().when(loan.getLoanTransactions(any())).thenReturn(List.of(repayment));
|
||||
|
||||
LoanTransaction chargeBackTransaction = createChargebackTransaction(loan);
|
||||
MoneyHolder overpaymentHolder = new MoneyHolder(Money.zero(MONETARY_CURRENCY));
|
||||
List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
|
||||
LoanRepaymentScheduleInstallment installment1 = createMockInstallment(LocalDate.of(2022, 12, 20), false);
|
||||
LoanRepaymentScheduleInstallment installment2 = createMockInstallment(LocalDate.of(2022, 12, 27), true);
|
||||
installments.add(installment1);
|
||||
installments.add(installment2);
|
||||
|
||||
// when
|
||||
underTest.processCreditTransaction(chargeBackTransaction, overpaymentHolder, MONETARY_CURRENCY, installments);
|
||||
|
||||
// then
|
||||
Mockito.verify(installment2, Mockito.times(1)).addToCredits(new BigDecimal("25.00"));
|
||||
Mockito.verify(installment2, Mockito.times(1)).updateInterestCharged(new BigDecimal("15.00"));
|
||||
ArgumentCaptor<LocalDate> localDateArgumentCaptor = ArgumentCaptor.forClass(LocalDate.class);
|
||||
ArgumentCaptor<Money> moneyCaptor = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(installment2, Mockito.times(1)).addToPrincipal(localDateArgumentCaptor.capture(), moneyCaptor.capture());
|
||||
Assertions.assertEquals(LocalDate.of(2023, 1, 1), localDateArgumentCaptor.getValue());
|
||||
assertEquals(0, moneyCaptor.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
|
||||
ArgumentCaptor<Money> principal = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> interest = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> fee = ArgumentCaptor.forClass(Money.class);
|
||||
ArgumentCaptor<Money> penalty = ArgumentCaptor.forClass(Money.class);
|
||||
Mockito.verify(chargeBackTransaction, times(1)).updateComponents(principal.capture(), interest.capture(), fee.capture(),
|
||||
penalty.capture());
|
||||
assertEquals(0, principal.getValue().getAmount().compareTo(BigDecimal.valueOf(10.0)));
|
||||
assertEquals(0, interest.getValue().getAmount().compareTo(BigDecimal.valueOf(15.0)));
|
||||
assertEquals(0, fee.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
assertEquals(0, penalty.getValue().getAmount().compareTo(BigDecimal.ZERO));
|
||||
}
|
||||
|
||||
private LoanRepaymentScheduleInstallment createMockInstallment(LocalDate localDate, boolean isAdditional) {
|
||||
LoanRepaymentScheduleInstallment installment = mock(LoanRepaymentScheduleInstallment.class);
|
||||
lenient().when(installment.isAdditional()).thenReturn(isAdditional);
|
||||
lenient().when(installment.getDueDate()).thenReturn(localDate);
|
||||
Money interestCharged = Money.of(MONETARY_CURRENCY, BigDecimal.ZERO);
|
||||
lenient().when(installment.getInterestCharged(MONETARY_CURRENCY)).thenReturn(interestCharged);
|
||||
return installment;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private LoanCreditAllocationRule createMockCreditAllocationRule(AllocationType... allocationTypes) {
|
||||
LoanCreditAllocationRule mockCreditAllocationRule = mock(LoanCreditAllocationRule.class);
|
||||
lenient().when(mockCreditAllocationRule.getTransactionType()).thenReturn(CreditAllocationTransactionType.CHARGEBACK);
|
||||
lenient().when(mockCreditAllocationRule.getAllocationTypes()).thenReturn(Arrays.asList(allocationTypes));
|
||||
return mockCreditAllocationRule;
|
||||
}
|
||||
|
||||
private LoanTransaction createRepayment(Loan loan) {
|
||||
LoanTransaction repayment = mock(LoanTransaction.class);
|
||||
lenient().when(repayment.getLoan()).thenReturn(loan);
|
||||
lenient().when(repayment.isRepayment()).thenReturn(true);
|
||||
lenient().when(repayment.getTypeOf()).thenReturn(REPAYMENT);
|
||||
lenient().when(repayment.getPrincipalPortion()).thenReturn(BigDecimal.valueOf(10));
|
||||
lenient().when(repayment.getInterestPortion()).thenReturn(BigDecimal.valueOf(20));
|
||||
lenient().when(repayment.getFeeChargesPortion()).thenReturn(BigDecimal.ZERO);
|
||||
lenient().when(repayment.getPenaltyChargesPortion()).thenReturn(BigDecimal.ZERO);
|
||||
return repayment;
|
||||
}
|
||||
|
||||
private LoanTransaction createChargebackTransaction(Loan loan) {
|
||||
LoanTransaction chargeback = mock(LoanTransaction.class);
|
||||
lenient().when(chargeback.isChargeback()).thenReturn(true);
|
||||
lenient().when(chargeback.getTypeOf()).thenReturn(CHARGEBACK);
|
||||
lenient().when(chargeback.getLoan()).thenReturn(loan);
|
||||
lenient().when(chargeback.getAmount()).thenReturn(BigDecimal.valueOf(25));
|
||||
Money amount = Money.of(MONETARY_CURRENCY, BigDecimal.valueOf(25));
|
||||
lenient().when(chargeback.getAmount(MONETARY_CURRENCY)).thenReturn(amount);
|
||||
lenient().when(chargeback.getTransactionDate()).thenReturn(LocalDate.of(2023, 1, 1));
|
||||
return chargeback;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateChargebackAllocationMap() {
|
||||
Map<AllocationType, Money> result;
|
||||
MonetaryCurrency currency = mock(MonetaryCurrency.class);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(50.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(50.0, 0, 0, 0), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, INTEREST, FEE, PENALTY), currency);
|
||||
verify(allocationMap(40.0, 10, 0, 0), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(50.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 0, 10, 0), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(340.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 88.0, 200.0, 12.0), result);
|
||||
|
||||
result = underTest.calculateChargebackAllocationMap(allocationMap(40.0, 100.0, 200.0, 12.0), BigDecimal.valueOf(352.0),
|
||||
List.of(PRINCIPAL, FEE, PENALTY, INTEREST), currency);
|
||||
verify(allocationMap(40.0, 100.0, 200.0, 12.0), result);
|
||||
}
|
||||
|
||||
private void verify(Map<AllocationType, BigDecimal> expected, Map<AllocationType, Money> actual) {
|
||||
Assertions.assertEquals(expected.size(), actual.size());
|
||||
expected.forEach((k, v) -> {
|
||||
Assertions.assertEquals(0, v.compareTo(actual.get(k).getAmount()), "Not matching for " + k);
|
||||
});
|
||||
}
|
||||
|
||||
private Map<AllocationType, BigDecimal> allocationMap(double principal, double interest, double fee, double penalty) {
|
||||
Map<AllocationType, BigDecimal> allocationMap = new HashMap<>();
|
||||
allocationMap.put(AllocationType.PRINCIPAL, BigDecimal.valueOf(principal));
|
||||
allocationMap.put(AllocationType.INTEREST, BigDecimal.valueOf(interest));
|
||||
allocationMap.put(AllocationType.FEE, BigDecimal.valueOf(fee));
|
||||
allocationMap.put(AllocationType.PENALTY, BigDecimal.valueOf(penalty));
|
||||
return allocationMap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+30
-7
@@ -61,6 +61,8 @@ import org.apache.fineract.client.models.PostLoansLoanIdChargesResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||
@@ -335,7 +337,7 @@ public abstract class BaseLoanIntegrationTest {
|
||||
&& Objects.equals(item.getPenaltyChargesPortion(), tr.penaltyPortion) //
|
||||
&& Objects.equals(item.getUnrecognizedIncomePortion(), tr.unrecognizedPortion) //
|
||||
);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr);
|
||||
Assertions.assertTrue(found, "Required transaction not found: " + tr);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -414,6 +416,14 @@ public abstract class BaseLoanIntegrationTest {
|
||||
"%d. installment's fee charges due is different, expected: %.2f, actual: %.2f".formatted(i, feeAmount, feeDue));
|
||||
}
|
||||
|
||||
Double penaltyAmount = installments[i].penaltyAmount;
|
||||
Double penaltyDue = period.getPenaltyChargesDue();
|
||||
if (penaltyAmount != null) {
|
||||
Assertions.assertEquals(penaltyAmount, penaltyDue,
|
||||
"%d. installment's penalty charges due is different, expected: %.2f, actual: %.2f".formatted(i, penaltyAmount,
|
||||
penaltyDue));
|
||||
}
|
||||
|
||||
Double outstandingAmount = installments[i].totalOutstandingAmount;
|
||||
Double totalOutstanding = period.getTotalOutstandingForPeriod();
|
||||
if (outstandingAmount != null) {
|
||||
@@ -497,10 +507,17 @@ public abstract class BaseLoanIntegrationTest {
|
||||
return applyAndApproveLoan(clientId, loanProductId, loanDisbursementDate, amount, 1);
|
||||
}
|
||||
|
||||
protected void addRepaymentForLoan(Long loanId, Double amount, String date) {
|
||||
protected Long addRepaymentForLoan(Long loanId, Double amount, String date) {
|
||||
String firstRepaymentUUID = UUID.randomUUID().toString();
|
||||
loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN)
|
||||
.transactionDate(date).locale("en").transactionAmount(amount).externalId(firstRepaymentUUID));
|
||||
PostLoansLoanIdTransactionsResponse response = loanTransactionHelper.makeLoanRepayment(loanId,
|
||||
new PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate(date).locale("en")
|
||||
.transactionAmount(amount).externalId(firstRepaymentUUID));
|
||||
return response.getResourceId();
|
||||
}
|
||||
|
||||
protected void addChargebackForLoan(Long loanId, Long transactionId, Double amount) {
|
||||
loanTransactionHelper.chargebackLoanTransaction(loanId, transactionId,
|
||||
new PostLoansLoanIdTransactionsTransactionIdRequest().locale("en").transactionAmount(amount).paymentTypeId(1L));
|
||||
}
|
||||
|
||||
protected PostChargesResponse createCharge(Double amount) {
|
||||
@@ -538,17 +555,22 @@ public abstract class BaseLoanIntegrationTest {
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, null, null, null, completed, dueDate);
|
||||
return new Installment(principalAmount, null, null, null, null, completed, dueDate);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double totalOutstandingAmount, Boolean completed,
|
||||
String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, null, totalOutstandingAmount, completed, dueDate);
|
||||
return new Installment(principalAmount, interestAmount, null, null, totalOutstandingAmount, completed, dueDate);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double totalOutstandingAmount,
|
||||
Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, totalOutstandingAmount, completed, dueDate);
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, null, totalOutstandingAmount, completed, dueDate);
|
||||
}
|
||||
|
||||
protected Installment installment(double principalAmount, double interestAmount, double feeAmount, double penaltyAmount,
|
||||
double totalOutstandingAmount, Boolean completed, String dueDate) {
|
||||
return new Installment(principalAmount, interestAmount, feeAmount, penaltyAmount, totalOutstandingAmount, completed, dueDate);
|
||||
}
|
||||
|
||||
protected BatchRequestBuilder batchRequest() {
|
||||
@@ -655,6 +677,7 @@ public abstract class BaseLoanIntegrationTest {
|
||||
Double principalAmount;
|
||||
Double interestAmount;
|
||||
Double feeAmount;
|
||||
Double penaltyAmount;
|
||||
Double totalOutstandingAmount;
|
||||
Boolean completed;
|
||||
String dueDate;
|
||||
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
/**
|
||||
* 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.integrationtests;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||
import org.apache.fineract.client.models.CreditAllocationData;
|
||||
import org.apache.fineract.client.models.CreditAllocationOrder;
|
||||
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansRequest;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
@Slf4j
|
||||
@ExtendWith(LoanTestLifecycleExtension.class)
|
||||
public class LoanChargebackWithCreditAllocationsIntegrationTests extends BaseLoanIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipal() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(charbackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 20 penalty + 50 fee + 0 interest + 30
|
||||
// principal
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPenaltyFeeInterestAndPrincipalOnNPlusOneInstallment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(charbackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date + and make a full repayment for the first installment
|
||||
updateBusinessDate("20 January 2023");
|
||||
addRepaymentForLoan(loanId, 313.0, "20 January 2023");
|
||||
|
||||
// Update Business Date + and make a full repayment for the second installment
|
||||
updateBusinessDate("20 February 2023");
|
||||
addRepaymentForLoan(loanId, 313.0, "20 February 2023");
|
||||
|
||||
// Update Business Date + and make a full repayment for the third installment
|
||||
updateBusinessDate("20 March 2023");
|
||||
addRepaymentForLoan(loanId, 313.0, "20 March 2023");
|
||||
|
||||
// Add some charges Update Business Date + and make a full repayment for the fourth installment
|
||||
updateBusinessDate("20 April 2023");
|
||||
addCharge(loanId, false, 50, "20 April 2023");
|
||||
addCharge(loanId, true, 20, "20 April 2023");
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 381.0, "20 April 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 50, 20, 0.0, true, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Let's move over the maturity date and chargeback some money
|
||||
updateBusinessDate("02 May 2023");
|
||||
|
||||
// Add Chargeback, 20 penalty + 50 fee + 0 interest + 30 principal
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(313.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(313.0, "Repayment", "20 February 2023", 624.0, 313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(313.0, "Repayment", "20 March 2023", 311.0, 313.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(381.0, "Repayment", "20 April 2023", 0.0, 311.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(70.0, "Accrual", "20 April 2023", 0.0, 0.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "02 May 2023", 100.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 0.0, true, "01 April 2023"), //
|
||||
installment(311.0, 0, 50, 20, 0.0, true, "01 May 2023"), //
|
||||
installment(30.0, 0, 0, 0, 30.0, false, "02 May 2023") // TODO: fee and penalty must be added here
|
||||
// after chargeback
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void createLoanWithCreditAllocationAndChargebackReverseReplayWithBackdatedPayment() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(charbackAllocation("PENALTY", "FEE", "INTEREST", "PRINCIPAL"));
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "15 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
updateBusinessDate("21 January 2023");
|
||||
|
||||
// Add Chargeback20 penalty + 50 fee + 0 interest + 30 principal
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 1037.0, 30.0, 0.0, 50.0, 20.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(343.0, 0, 50, 20, 30.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// let's add a backdated repayment on 19th of January reverse replaying the chargeback
|
||||
addRepaymentForLoan(loanId, 200.0, "19 January 2023");
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(200.0, "Repayment", "19 January 2023", 1120.0, 130.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 737.0, 383.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "21 January 2023", 937.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createLoanWithCreditAllocationAndChargebackPrincipalInterestFeePenalty() {
|
||||
runAt("01 January 2023", () -> {
|
||||
// Create Client
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
// Create Loan Product
|
||||
Long loanProductId = createLoanProduct(charbackAllocation("PRINCIPAL", "INTEREST", "FEE", "PENALTY"));
|
||||
|
||||
// Apply and Approve Loan
|
||||
Long loanId = applyAndApproveLoan(clientId, loanProductId);
|
||||
|
||||
// Disburse Loan
|
||||
disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023");
|
||||
|
||||
// Add Charges
|
||||
Long feeId = addCharge(loanId, false, 50, "15 January 2023");
|
||||
Long penaltyId = addCharge(loanId, true, 20, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 383.0, false, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Update Business Date
|
||||
updateBusinessDate("20 January 2023");
|
||||
|
||||
// Add Repayment
|
||||
Long repaymentTransaction = addRepaymentForLoan(loanId, 383.0, "20 January 2023");
|
||||
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(313.0, 0, 50, 20, 0.0, true, "01 February 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
|
||||
// Add Chargeback
|
||||
addChargebackForLoan(loanId, repaymentTransaction, 100.0); // 100 principal, 0 interest, 0 fee 0 penalty
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(1250.0, "Disbursement", "01 January 2023", 1250.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
|
||||
transaction(383.0, "Repayment", "20 January 2023", 937.0, 313.0, 0.0, 50.0, 20.0, 0.0, 0.0), //
|
||||
transaction(100.0, "Chargeback", "20 January 2023", 1037.0, 100.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
|
||||
);
|
||||
|
||||
// Verify Repayment Schedule
|
||||
verifyRepaymentSchedule(loanId, //
|
||||
installment(0, null, "01 January 2023"), //
|
||||
installment(413.0, 0, 50, 20, 100.0, false, "01 February 2023"), // TODO: we still need to add the
|
||||
// fee and the penalty to the
|
||||
// outstanding
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 March 2023"), //
|
||||
installment(313.0, 0, 0, 0, 313.0, false, "01 April 2023"), //
|
||||
installment(311.0, 0, 0, 0, 311.0, false, "01 May 2023") //
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Long applyAndApproveLoan(Long clientId, Long loanProductId) {
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 January 2023", 1250.0, 4)//
|
||||
.repaymentEvery(1)//
|
||||
.loanTermFrequency(4)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.loanTermFrequencyType(RepaymentFrequencyType.MONTHS)//
|
||||
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy");
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(1250.0, "01 January 2023"));
|
||||
|
||||
Long loanId = approvedLoanResult.getLoanId();
|
||||
return loanId;
|
||||
}
|
||||
|
||||
public Long createLoanProduct(CreditAllocationData... creditAllocationData) {
|
||||
PostLoanProductsRequest postLoanProductsRequest = loanProductWithAdvancedPaymentAllocationWith4Installments(creditAllocationData);
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(postLoanProductsRequest);
|
||||
return loanProductResponse.getResourceId();
|
||||
}
|
||||
|
||||
private PostLoanProductsRequest loanProductWithAdvancedPaymentAllocationWith4Installments(
|
||||
CreditAllocationData... creditAllocationData) {
|
||||
return createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct().numberOfRepayments(4)//
|
||||
.repaymentEvery(1)//
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())//
|
||||
.loanScheduleType(LoanScheduleType.PROGRESSIVE.toString()) //
|
||||
.loanScheduleProcessingType(LoanScheduleProcessingType.VERTICAL.toString()) //
|
||||
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")
|
||||
.paymentAllocation(List.of(createDefaultPaymentAllocation(), createRepaymentPaymentAllocation()))
|
||||
.creditAllocation(Arrays.asList(creditAllocationData));
|
||||
}
|
||||
|
||||
private AdvancedPaymentData createDefaultPaymentAllocation() {
|
||||
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
|
||||
advancedPaymentData.setTransactionType("DEFAULT");
|
||||
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
|
||||
|
||||
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
|
||||
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_PRINCIPAL, PaymentAllocationType.PAST_DUE_INTEREST,
|
||||
PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_PRINCIPAL,
|
||||
PaymentAllocationType.DUE_INTEREST, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
|
||||
PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST);
|
||||
|
||||
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
|
||||
return advancedPaymentData;
|
||||
}
|
||||
|
||||
private AdvancedPaymentData createRepaymentPaymentAllocation() {
|
||||
AdvancedPaymentData advancedPaymentData = new AdvancedPaymentData();
|
||||
advancedPaymentData.setTransactionType("REPAYMENT");
|
||||
advancedPaymentData.setFutureInstallmentAllocationRule("NEXT_INSTALLMENT");
|
||||
|
||||
List<PaymentAllocationOrder> paymentAllocationOrders = getPaymentAllocationOrder(PaymentAllocationType.PAST_DUE_PENALTY,
|
||||
PaymentAllocationType.PAST_DUE_FEE, PaymentAllocationType.PAST_DUE_INTEREST, PaymentAllocationType.PAST_DUE_PRINCIPAL,
|
||||
PaymentAllocationType.DUE_PENALTY, PaymentAllocationType.DUE_FEE, PaymentAllocationType.DUE_INTEREST,
|
||||
PaymentAllocationType.DUE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_PENALTY, PaymentAllocationType.IN_ADVANCE_FEE,
|
||||
PaymentAllocationType.IN_ADVANCE_PRINCIPAL, PaymentAllocationType.IN_ADVANCE_INTEREST);
|
||||
|
||||
advancedPaymentData.setPaymentAllocationOrder(paymentAllocationOrders);
|
||||
return advancedPaymentData;
|
||||
}
|
||||
|
||||
private CreditAllocationData charbackAllocation(String... allocationRules) {
|
||||
CreditAllocationData creditAllocationData = new CreditAllocationData();
|
||||
creditAllocationData.setTransactionType("CHARGEBACK");
|
||||
creditAllocationData.setCreditAllocationOrder(createCreditAllocationOrders(allocationRules));
|
||||
return creditAllocationData;
|
||||
}
|
||||
|
||||
public List<CreditAllocationOrder> createCreditAllocationOrders(String... allocationRule) {
|
||||
AtomicInteger integer = new AtomicInteger(1);
|
||||
return Arrays.stream(allocationRule).map(allocation -> {
|
||||
CreditAllocationOrder creditAllocationOrder = new CreditAllocationOrder();
|
||||
creditAllocationOrder.setCreditAllocationRule(allocation);
|
||||
creditAllocationOrder.setOrder(integer.getAndIncrement());
|
||||
return creditAllocationOrder;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private List<PaymentAllocationOrder> getPaymentAllocationOrder(PaymentAllocationType... paymentAllocationTypes) {
|
||||
AtomicInteger integer = new AtomicInteger(1);
|
||||
return Arrays.stream(paymentAllocationTypes).map(pat -> {
|
||||
PaymentAllocationOrder paymentAllocationOrder = new PaymentAllocationOrder();
|
||||
paymentAllocationOrder.setPaymentAllocationRule(pat.name());
|
||||
paymentAllocationOrder.setOrder(integer.getAndIncrement());
|
||||
return paymentAllocationOrder;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
}
|
||||
+5
@@ -744,6 +744,11 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return ok(fineract().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId, request, "undo"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final Long loanId, final Long transactionId,
|
||||
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
|
||||
return ok(fineract().loanTransactions.adjustLoanTransaction(loanId, transactionId, request, "chargeback"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse chargebackLoanTransaction(final String loanExternalId, final Long transactionId,
|
||||
final PostLoansLoanIdTransactionsTransactionIdRequest request) {
|
||||
return ok(fineract().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId, request, "chargeback"));
|
||||
|
||||
Reference in New Issue
Block a user