FINERACT-1968: Advanced payment allocation - Default allocation
This commit is contained in:
+3
-1
@@ -25,6 +25,7 @@ import jakarta.ws.rs.core.Response;
|
||||
import jakarta.ws.rs.ext.ExceptionMapper;
|
||||
import java.util.Map;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@@ -40,7 +41,8 @@ public class DefaultExceptionMapper implements FineractExceptionMapper, Exceptio
|
||||
|
||||
@Override
|
||||
public Response toResponse(RuntimeException runtimeException) {
|
||||
return Response.status(SC_INTERNAL_SERVER_ERROR).entity(Map.of("Exception", runtimeException.getMessage()))
|
||||
return Response.status(SC_INTERNAL_SERVER_ERROR)
|
||||
.entity(Map.of("Exception", ObjectUtils.defaultIfNull(runtimeException.getMessage(), "No error message available")))
|
||||
.type(MediaType.APPLICATION_JSON).build();
|
||||
}
|
||||
}
|
||||
|
||||
+8
@@ -433,6 +433,14 @@ public final class MathUtil {
|
||||
return min(min(first, second, notNull), third, notNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate percentage of a value
|
||||
*
|
||||
* @param value
|
||||
* @param percentage
|
||||
* @param precision
|
||||
* @return
|
||||
*/
|
||||
public static BigDecimal percentageOf(final BigDecimal value, final BigDecimal percentage, final int precision) {
|
||||
BigDecimal percentageOf = BigDecimal.ZERO;
|
||||
if (isGreaterThanZero(value)) {
|
||||
|
||||
+12
-12
@@ -702,7 +702,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
|
||||
@@ -863,7 +863,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
* affected Transactions
|
||||
***/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
}
|
||||
this.charges.remove(loanCharge);
|
||||
@@ -931,7 +931,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
* affected Transactions
|
||||
***/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
} else {
|
||||
// reprocess loan schedule based on charge been waived.
|
||||
@@ -1124,7 +1124,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
* affected Transactions
|
||||
***/
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
} else {
|
||||
// reprocess loan schedule based on charge been waived.
|
||||
@@ -2706,7 +2706,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (!allNonContraTransactionsPostDisbursement.isEmpty()) {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
@@ -3351,7 +3351,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
@@ -3779,7 +3779,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
if (this.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -3928,7 +3928,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
regenerateRepaymentScheduleWithInterestRecalculation(scheduleGeneratorDTO);
|
||||
}
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
@@ -5402,7 +5402,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
@@ -5545,7 +5545,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory
|
||||
.determineProcessor(this.transactionProcessingStrategyCode);
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(
|
||||
ChangedTransactionDetail changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(
|
||||
getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(),
|
||||
getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
@@ -5788,7 +5788,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
for (LoanTransaction loanTransaction : allNonContraTransactionsPostDisbursement) {
|
||||
copyTransactions.add(LoanTransaction.copyTransactionProperties(loanTransaction));
|
||||
}
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(), copyTransactions, getCurrency(),
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(), copyTransactions, getCurrency(),
|
||||
getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
|
||||
updateLoanSummaryDerivedFields();
|
||||
@@ -6392,7 +6392,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom {
|
||||
getRepaymentScheduleInstallments(), getActiveCharges(), getTotalOverpaidAsMoney());
|
||||
} else {
|
||||
final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retrieveListOfTransactionsPostDisbursement();
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(getDisbursementDate(),
|
||||
changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(getDisbursementDate(),
|
||||
allNonContraTransactionsPostDisbursement, getCurrency(), getRepaymentScheduleInstallments(), getActiveCharges());
|
||||
for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail.getNewTransactionMappings().entrySet()) {
|
||||
mapEntry.getValue().updateLoan(this);
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ public interface LoanRepaymentScheduleTransactionProcessor {
|
||||
* before existing transactions or and adjustment is made to an existing in which case the entire loan schedule
|
||||
* needs to be re-processed.
|
||||
*/
|
||||
ChangedTransactionDetail reprocessLoanTranactions(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
|
||||
ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate, List<LoanTransaction> repaymentsOrWaivers,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
|
||||
|
||||
Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
|
||||
+9
-1
@@ -27,7 +27,15 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
public enum PaymentAllocationTransactionType {
|
||||
|
||||
DEFAULT(null), //
|
||||
REPAYMENT(LoanTransactionType.REPAYMENT); //
|
||||
REPAYMENT(LoanTransactionType.REPAYMENT), //
|
||||
DOWN_PAYMENT(LoanTransactionType.DOWN_PAYMENT), //
|
||||
MERCHANT_ISSUED_REFUND(LoanTransactionType.MERCHANT_ISSUED_REFUND), //
|
||||
PAYOUT_REFUND(LoanTransactionType.PAYOUT_REFUND), //
|
||||
GOODWILL_CREDIT(LoanTransactionType.GOODWILL_CREDIT), //
|
||||
CHARGE_REFUND(LoanTransactionType.CHARGE_REFUND), //
|
||||
CHARGE_ADJUSTMENT(LoanTransactionType.CHARGE_ADJUSTMENT), //
|
||||
WAIVE_INTEREST(LoanTransactionType.WAIVE_INTEREST),//
|
||||
; //
|
||||
|
||||
private final LoanTransactionType loanTransactionType;
|
||||
}
|
||||
|
||||
+12
-12
@@ -64,7 +64,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangedTransactionDetail reprocessLoanTranactions(final LocalDate disbursementDate,
|
||||
public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbursementDate,
|
||||
final List<LoanTransaction> transactionsPostDisbursement, final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
|
||||
|
||||
@@ -382,7 +382,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
}
|
||||
|
||||
private void reprocessInstallments(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
|
||||
protected void reprocessInstallments(List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency) {
|
||||
LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
|
||||
if (lastInstallment.isAdditional() && lastInstallment.getDue(currency).isZero()) {
|
||||
installments.remove(lastInstallment);
|
||||
@@ -413,7 +413,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return mergedList;
|
||||
}
|
||||
|
||||
private void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||
ChangedTransactionDetail changedTransactionDetail) {
|
||||
loanTransaction.reverse();
|
||||
loanTransaction.updateExternalId(null);
|
||||
@@ -538,7 +538,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
}
|
||||
}
|
||||
|
||||
private Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
protected Money handleTransactionAndCharges(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, final Money chargeAmountToProcess,
|
||||
final boolean isFeeCharge) {
|
||||
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||
@@ -574,7 +574,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return transactionAmountUnprocessed;
|
||||
}
|
||||
|
||||
private Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
protected Money processTransaction(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges, Money amountToProcess) {
|
||||
int installmentIndex = 0;
|
||||
|
||||
@@ -607,7 +607,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return transactionAmountUnprocessed;
|
||||
}
|
||||
|
||||
private Set<LoanCharge> extractFeeCharges(final Set<LoanCharge> loanCharges) {
|
||||
protected Set<LoanCharge> extractFeeCharges(final Set<LoanCharge> loanCharges) {
|
||||
final Set<LoanCharge> feeCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isFeeCharge()) {
|
||||
@@ -617,7 +617,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return feeCharges;
|
||||
}
|
||||
|
||||
private Set<LoanCharge> extractPenaltyCharges(final Set<LoanCharge> loanCharges) {
|
||||
protected Set<LoanCharge> extractPenaltyCharges(final Set<LoanCharge> loanCharges) {
|
||||
final Set<LoanCharge> penaltyCharges = new HashSet<>();
|
||||
for (final LoanCharge loanCharge : loanCharges) {
|
||||
if (loanCharge.isPenaltyCharge()) {
|
||||
@@ -627,7 +627,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return penaltyCharges;
|
||||
}
|
||||
|
||||
private void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
|
||||
protected void updateChargesPaidAmountBy(final LoanTransaction loanTransaction, final Money feeCharges, final Set<LoanCharge> charges,
|
||||
final Integer installmentNumber) {
|
||||
|
||||
Money amountRemaining = feeCharges;
|
||||
@@ -661,7 +661,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
|
||||
}
|
||||
|
||||
private LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
|
||||
protected LoanCharge findEarliestUnpaidChargeFromUnOrderedSet(final Set<LoanCharge> charges, final MonetaryCurrency currency) {
|
||||
LoanCharge earliestUnpaidCharge = null;
|
||||
LoanCharge installemntCharge = null;
|
||||
LoanInstallmentCharge chargePerInstallment = null;
|
||||
@@ -687,7 +687,7 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
return earliestUnpaidCharge;
|
||||
}
|
||||
|
||||
private void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
protected void handleWriteOff(final LoanTransaction loanTransaction, final MonetaryCurrency currency,
|
||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
final LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
@@ -712,12 +712,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
||||
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
|
||||
}
|
||||
|
||||
private void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
|
||||
protected void handleChargeback(LoanTransaction loanTransaction, MonetaryCurrency currency, Money overpaidAmount,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
processCreditTransaction(loanTransaction, overpaidAmount, currency, installments);
|
||||
}
|
||||
|
||||
private void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
protected void handleRefund(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, final Set<LoanCharge> charges) {
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
final Comparator<LoanRepaymentScheduleInstallment> byDate = Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate);
|
||||
|
||||
+288
-5
@@ -18,19 +18,37 @@
|
||||
*/
|
||||
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.NotImplementedException;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
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.LoanCharge;
|
||||
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.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType;
|
||||
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||
import org.springframework.context.annotation.Profile;
|
||||
|
||||
//TODO: remove `test` profile when it is finished
|
||||
@Profile("test")
|
||||
@Slf4j
|
||||
public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRepaymentScheduleTransactionProcessor {
|
||||
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||
@@ -74,20 +92,285 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChangedTransactionDetail reprocessLoanTranactions(LocalDate disbursementDate, List<LoanTransaction> transactionsPostDisbursement,
|
||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||
throw new NotImplementedException();
|
||||
public ChangedTransactionDetail reprocessLoanTransactions(LocalDate disbursementDate,
|
||||
List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||
|
||||
// TODO: rewrite this whole logic step by step
|
||||
if (charges != null) {
|
||||
for (final LoanCharge loanCharge : charges) {
|
||||
if (!loanCharge.isDueAtDisbursement()) {
|
||||
loanCharge.resetPaidAmount(currency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||
currentInstallment.resetDerivedComponents();
|
||||
currentInstallment.updateDerivedFields(currency, disbursementDate);
|
||||
}
|
||||
|
||||
// TODO: Remove this reprocess and add the charges to the installment in chronological order
|
||||
final LoanRepaymentScheduleProcessingWrapper wrapper = new LoanRepaymentScheduleProcessingWrapper();
|
||||
wrapper.reprocess(currency, disbursementDate, installments, charges);
|
||||
final ChangedTransactionDetail changedTransactionDetail = new ChangedTransactionDetail();
|
||||
for (final LoanTransaction loanTransaction : transactionsPostDisbursement) {
|
||||
if (loanTransaction.getId() == null) {
|
||||
processLatestTransaction(loanTransaction, currency, installments, charges, null);
|
||||
loanTransaction.adjustInterestComponent(currency);
|
||||
} else {
|
||||
/**
|
||||
* For existing transactions, check if the re-payment breakup (principal, interest, fees, penalties) has
|
||||
* changed.<br>
|
||||
**/
|
||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||
|
||||
// Reset derived component of new loan transaction and
|
||||
// re-process transaction
|
||||
processLatestTransaction(newLoanTransaction, currency, installments, charges, null);
|
||||
newLoanTransaction.adjustInterestComponent(currency);
|
||||
/**
|
||||
* Check if the transaction amounts have changed. If so, reverse the original transaction and update
|
||||
* changedTransactionDetail accordingly
|
||||
**/
|
||||
if (LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(
|
||||
newLoanTransaction.getLoanTransactionToRepaymentScheduleMappings());
|
||||
} else {
|
||||
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
||||
}
|
||||
}
|
||||
}
|
||||
reprocessInstallments(installments, currency);
|
||||
return changedTransactionDetail;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processLatestTransaction(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges, Money overpaidAmount) {
|
||||
throw new NotImplementedException();
|
||||
switch (loanTransaction.getTypeOf()) {
|
||||
case WRITEOFF -> handleWriteOff(loanTransaction, currency, installments);
|
||||
case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, currency, installments, charges);
|
||||
case CHARGEBACK -> handleChargeback(loanTransaction, currency, overpaidAmount, installments);
|
||||
|
||||
case REPAYMENT, MERCHANT_ISSUED_REFUND, PAYOUT_REFUND, GOODWILL_CREDIT, CHARGE_REFUND, CHARGE_ADJUSTMENT, DOWN_PAYMENT,
|
||||
WAIVE_INTEREST, RECOVERY_REPAYMENT ->
|
||||
handleRepayment(loanTransaction, currency, installments, charges);
|
||||
// TODO: Cover rest of the transaction types
|
||||
default -> {
|
||||
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Money handleRepaymentSchedule(List<LoanTransaction> transactionsPostDisbursement, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
|
||||
return super.handleRepaymentSchedule(transactionsPostDisbursement, currency, installments, loanCharges);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private void handleRepayment(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> charges) {
|
||||
if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
}
|
||||
Money transactionAmountUnprocessed = loanTransaction.getAmount(currency);
|
||||
Money zero = Money.zero(currency);
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings = new ArrayList<>();
|
||||
|
||||
List<LoanPaymentAllocationRule> paymentAllocationRules = loanTransaction.getLoan().getPaymentAllocationRules();
|
||||
LoanPaymentAllocationRule defaultPaymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> PaymentAllocationTransactionType.DEFAULT.equals(e.getTransactionType())).findFirst().orElseThrow();
|
||||
LoanPaymentAllocationRule paymentAllocationRule = paymentAllocationRules.stream()
|
||||
.filter(e -> loanTransaction.getTypeOf().equals(e.getTransactionType().getLoanTransactionType())).findFirst()
|
||||
.orElse(defaultPaymentAllocationRule);
|
||||
Balances balances = new Balances(zero, zero, zero, zero);
|
||||
for (PaymentAllocationType paymentAllocationType : paymentAllocationRule.getAllocationTypes()) {
|
||||
FutureInstallmentAllocationRule futureInstallmentAllocationRule = paymentAllocationRule.getFutureInstallmentAllocationRule();
|
||||
LoanRepaymentScheduleInstallment currentInstallment = null;
|
||||
Money paidPortion = zero;
|
||||
do {
|
||||
switch (paymentAllocationType.getDueType()) {
|
||||
case PAST_DUE -> {
|
||||
currentInstallment = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(e -> loanTransaction.getTransactionDate().isAfter(e.getDueDate()))
|
||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
if (currentInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||
paidPortion = payAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances);
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||
}
|
||||
}
|
||||
case DUE -> {
|
||||
currentInstallment = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(e -> loanTransaction.getTransactionDate().isEqual(e.getDueDate()))
|
||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).orElse(null);
|
||||
if (currentInstallment != null) {
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||
paidPortion = payAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||
transactionAmountUnprocessed, loanTransactionToRepaymentScheduleMapping, balances);
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(paidPortion);
|
||||
}
|
||||
}
|
||||
case IN_ADVANCE -> {
|
||||
// For having similar logic we are populating installment list even when the future installment
|
||||
// allocation rule is NEXT_INSTALLMENT or LAST_INSTALLMENT hence the list has only one element.
|
||||
List<LoanRepaymentScheduleInstallment> currentInstallments = new ArrayList<>();
|
||||
if (FutureInstallmentAllocationRule.REAMORTIZATION.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate())).toList();
|
||||
} else if (FutureInstallmentAllocationRule.NEXT_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate()))
|
||||
.min(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
} else if (FutureInstallmentAllocationRule.LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
|
||||
currentInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
|
||||
.filter(e -> loanTransaction.getTransactionDate().isBefore(e.getDueDate()))
|
||||
.max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
|
||||
}
|
||||
int numberOfInstallments = currentInstallments.size();
|
||||
paidPortion = zero;
|
||||
if (numberOfInstallments > 0) {
|
||||
// This will be the same amount as transactionAmountUnprocessed in case of the future
|
||||
// installment allocation is NEXT_INSTALLMENT or LAST_INSTALLMENT
|
||||
Money evenPortion = transactionAmountUnprocessed.dividedBy(numberOfInstallments, MoneyHelper.getRoundingMode());
|
||||
// Adjustment might be needed due to the divide operation and the rounding mode
|
||||
Money balanceAdjustment = transactionAmountUnprocessed.minus(evenPortion.multipliedBy(numberOfInstallments));
|
||||
for (LoanRepaymentScheduleInstallment internalCurrentInstallment : currentInstallments) {
|
||||
currentInstallment = internalCurrentInstallment;
|
||||
// Adjust the portion for the last installment
|
||||
if (internalCurrentInstallment.equals(currentInstallments.get(numberOfInstallments - 1))) {
|
||||
evenPortion = evenPortion.add(balanceAdjustment);
|
||||
}
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = getTransactionMapping(
|
||||
transactionMappings, loanTransaction, currentInstallment, currency);
|
||||
Money internalPaidPortion = payAllocation(paymentAllocationType, currentInstallment, loanTransaction,
|
||||
evenPortion, loanTransactionToRepaymentScheduleMapping, balances);
|
||||
// Some extra logic to allocate as much as possible across the installments if the
|
||||
// outstanding balances are different
|
||||
if (internalPaidPortion.isGreaterThanZero()) {
|
||||
paidPortion = internalPaidPortion;
|
||||
}
|
||||
transactionAmountUnprocessed = transactionAmountUnprocessed.minus(internalPaidPortion);
|
||||
}
|
||||
} else {
|
||||
currentInstallment = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// We are allocating till there is no pending installment or there is no more unprocessed transaction amount
|
||||
// or there is no more outstanding balance of the allocation type
|
||||
while (currentInstallment != null && transactionAmountUnprocessed.isGreaterThanZero() && paidPortion.isGreaterThanZero());
|
||||
|
||||
}
|
||||
loanTransaction.updateComponents(balances.getAggregatedPrincipalPortion(), balances.getAggregatedInterestPortion(),
|
||||
balances.getAggregatedFeeChargesPortion(), balances.getAggregatedPenaltyChargesPortion());
|
||||
loanTransaction.updateLoanTransactionToRepaymentScheduleMappings(transactionMappings);
|
||||
|
||||
// TODO: rewrite to provide sorted list
|
||||
final Set<LoanCharge> loanFees = extractFeeCharges(charges);
|
||||
final Set<LoanCharge> loanPenalties = extractPenaltyCharges(charges);
|
||||
|
||||
if (loanTransaction.isNotWaiver() && !loanTransaction.isAccrual()) {
|
||||
Money feeCharges = loanTransaction.getFeeChargesPortion(currency);
|
||||
Money penaltyCharges = loanTransaction.getPenaltyChargesPortion(currency);
|
||||
if (feeCharges.isGreaterThanZero()) {
|
||||
// TODO: Rewrite to exclude charge payment
|
||||
updateChargesPaidAmountBy(loanTransaction, feeCharges, loanFees, null);
|
||||
}
|
||||
|
||||
if (penaltyCharges.isGreaterThanZero()) {
|
||||
// TODO: Rewrite to exclude charge payment
|
||||
updateChargesPaidAmountBy(loanTransaction, penaltyCharges, loanPenalties, null);
|
||||
}
|
||||
}
|
||||
handleOverpayment(transactionAmountUnprocessed, loanTransaction);
|
||||
}
|
||||
|
||||
private LoanTransactionToRepaymentScheduleMapping getTransactionMapping(
|
||||
List<LoanTransactionToRepaymentScheduleMapping> transactionMappings, LoanTransaction loanTransaction,
|
||||
LoanRepaymentScheduleInstallment currentInstallment, MonetaryCurrency currency) {
|
||||
Money zero = Money.zero(currency);
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping = transactionMappings.stream()
|
||||
.filter(e -> loanTransaction.equals(e.getLoanTransaction()))
|
||||
.filter(e -> currentInstallment.equals(e.getLoanRepaymentScheduleInstallment())).findFirst().orElse(null);
|
||||
if (loanTransactionToRepaymentScheduleMapping == null) {
|
||||
loanTransactionToRepaymentScheduleMapping = LoanTransactionToRepaymentScheduleMapping.createFrom(loanTransaction,
|
||||
currentInstallment, zero, zero, zero, zero);
|
||||
transactionMappings.add(loanTransactionToRepaymentScheduleMapping);
|
||||
}
|
||||
return loanTransactionToRepaymentScheduleMapping;
|
||||
}
|
||||
|
||||
private Money payAllocation(PaymentAllocationType paymentAllocationType, LoanRepaymentScheduleInstallment currentInstallment,
|
||||
LoanTransaction loanTransaction, Money transactionAmountUnprocessed,
|
||||
LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping, Balances balances) {
|
||||
LocalDate transactionDate = loanTransaction.getTransactionDate();
|
||||
Money zero = transactionAmountUnprocessed.zero();
|
||||
return switch (paymentAllocationType.getAllocationType()) {
|
||||
case PENALTY -> {
|
||||
Money subPenaltyPortion = currentInstallment.payPenaltyChargesComponent(transactionDate, transactionAmountUnprocessed);
|
||||
balances.setAggregatedPenaltyChargesPortion(balances.getAggregatedPenaltyChargesPortion().add(subPenaltyPortion));
|
||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, zero, subPenaltyPortion);
|
||||
yield subPenaltyPortion;
|
||||
}
|
||||
case FEE -> {
|
||||
Money subFeePortion = currentInstallment.payFeeChargesComponent(transactionDate, transactionAmountUnprocessed);
|
||||
balances.setAggregatedFeeChargesPortion(balances.getAggregatedFeeChargesPortion().add(subFeePortion));
|
||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, zero, subFeePortion, zero);
|
||||
yield subFeePortion;
|
||||
}
|
||||
case INTEREST -> {
|
||||
Money subInterestPortion = currentInstallment.payInterestComponent(transactionDate, transactionAmountUnprocessed);
|
||||
balances.setAggregatedInterestPortion(balances.getAggregatedInterestPortion().add(subInterestPortion));
|
||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, zero, subInterestPortion, zero, zero);
|
||||
yield subInterestPortion;
|
||||
}
|
||||
case PRINCIPAL -> {
|
||||
Money subPrincipalPortion = currentInstallment.payPrincipalComponent(transactionDate, transactionAmountUnprocessed);
|
||||
balances.setAggregatedPrincipalPortion(balances.getAggregatedPrincipalPortion().add(subPrincipalPortion));
|
||||
addToTransactionMapping(loanTransactionToRepaymentScheduleMapping, subPrincipalPortion, zero, zero, zero);
|
||||
yield subPrincipalPortion;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void addToTransactionMapping(LoanTransactionToRepaymentScheduleMapping loanTransactionToRepaymentScheduleMapping,
|
||||
Money principalPortion, Money interestPortion, Money feePortion, Money penaltyPortion) {
|
||||
BigDecimal aggregatedPenalty = ObjectUtils
|
||||
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPenaltyChargesPortion(), BigDecimal.ZERO)
|
||||
.add(penaltyPortion.getAmount());
|
||||
BigDecimal aggregatedFee = ObjectUtils
|
||||
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getFeeChargesPortion(), BigDecimal.ZERO)
|
||||
.add(feePortion.getAmount());
|
||||
BigDecimal aggregatedInterest = ObjectUtils
|
||||
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getInterestPortion(), BigDecimal.ZERO)
|
||||
.add(interestPortion.getAmount());
|
||||
BigDecimal aggregatedPrincipal = ObjectUtils
|
||||
.defaultIfNull(loanTransactionToRepaymentScheduleMapping.getPrincipalPortion(), BigDecimal.ZERO)
|
||||
.add(principalPortion.getAmount());
|
||||
loanTransactionToRepaymentScheduleMapping.setComponents(aggregatedPrincipal, aggregatedInterest, aggregatedFee, aggregatedPenalty);
|
||||
}
|
||||
|
||||
private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTransaction) {
|
||||
if (overpaymentPortion.isGreaterThanZero()) {
|
||||
onLoanOverpayment(loanTransaction, overpaymentPortion);
|
||||
loanTransaction.updateOverPayments(overpaymentPortion);
|
||||
}
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
private static class Balances {
|
||||
|
||||
private Money aggregatedPrincipalPortion;
|
||||
private Money aggregatedFeeChargesPortion;
|
||||
private Money aggregatedInterestPortion;
|
||||
private Money aggregatedPenaltyChargesPortion;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
|
||||
boolean ignoreDueDateCheck = false;
|
||||
boolean rerun = false;
|
||||
|
||||
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid)
|
||||
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isChargePending)
|
||||
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null
|
||||
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate))
|
||||
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
|
||||
boolean ignoreDueDateCheck = false;
|
||||
boolean rerun = false;
|
||||
|
||||
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isNotFullyPaid)
|
||||
List<LoanCharge> orderedLoanChargesByDueDate = charges.stream().filter(LoanCharge::isActive).filter(LoanCharge::isChargePending)
|
||||
.filter(loanCharge -> loanCharge.getEffectiveDueDate() == null
|
||||
|| !loanCharge.getEffectiveDueDate().isAfter(transactionDate))
|
||||
.sorted(LoanChargeEffectiveDueDateComparator.INSTANCE).toList();
|
||||
|
||||
+1
-1
@@ -2808,7 +2808,7 @@ public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGener
|
||||
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
||||
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsPostDisbursementExcludeAccruals();
|
||||
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTranactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||
Money feeCharges = Money.zero(currency);
|
||||
Money penaltyCharges = Money.zero(currency);
|
||||
|
||||
+6
-6
@@ -437,13 +437,13 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.plusDays(1));
|
||||
|
||||
Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2));
|
||||
@@ -478,13 +478,13 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1));
|
||||
Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(one);
|
||||
@@ -521,13 +521,13 @@ public class DuePenFeeIntPriInAdvancePriPenFeeIntLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1));
|
||||
Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven);
|
||||
|
||||
+6
-6
@@ -405,13 +405,13 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.plusDays(1));
|
||||
|
||||
Mockito.when(charges.stream()).thenReturn(Stream.of(loanCharge1, loanCharge2));
|
||||
@@ -445,13 +445,13 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(two);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1));
|
||||
Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(one);
|
||||
@@ -485,13 +485,13 @@ public class DuePenIntPriFeeInAdvancePenIntPriFeeLoanRepaymentScheduleTransactio
|
||||
|
||||
LoanCharge loanCharge1 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge1.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getEffectiveDueDate()).thenReturn(transactionDate);
|
||||
Mockito.when(loanCharge1.isPenaltyCharge()).thenReturn(true);
|
||||
Mockito.when(loanCharge1.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven);
|
||||
LoanCharge loanCharge2 = Mockito.mock(LoanCharge.class);
|
||||
Mockito.when(loanCharge2.isActive()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isNotFullyPaid()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isChargePending()).thenReturn(true);
|
||||
Mockito.when(loanCharge2.isPenaltyCharge()).thenReturn(false);
|
||||
Mockito.when(loanCharge2.getEffectiveDueDate()).thenReturn(transactionDate.minusDays(1));
|
||||
Mockito.when(loanCharge2.getAmount(refEq(MONETARY_CURRENCY))).thenReturn(eleven);
|
||||
|
||||
+1809
File diff suppressed because it is too large
Load Diff
+6
@@ -61,6 +61,8 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
|
||||
import org.apache.fineract.client.models.GetPaymentTypesResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdResponse;
|
||||
import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
|
||||
@@ -1906,6 +1908,10 @@ public class LoanTransactionHelper extends IntegrationTest {
|
||||
return ok(fineract().loanProducts.updateLoanProduct(id, requestModifyLoan));
|
||||
}
|
||||
|
||||
public PostLoanProductsResponse createLoanProduct(PostLoanProductsRequest request) {
|
||||
return ok(fineract().loanProducts.createLoanProduct(request));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse makeLoanDownPayment(String loanExternalId, PostLoansLoanIdTransactionsRequest request) {
|
||||
return ok(fineract().loanTransactions.executeLoanTransaction1(loanExternalId, request, "downPayment"));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user