FINERACT-2104: Accrual Activity Reverse Replay Transaction
This commit is contained in:
+14
-12
@@ -2481,24 +2481,19 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() {
|
public List<LoanTransaction> retrieveListOfTransactionsForReprocessing() {
|
||||||
return getLoanTransactions().stream()
|
return getLoanTransactions().stream().filter(loanTransactionForReprocessingPredicate()).sorted(LoanTransactionComparator.INSTANCE)
|
||||||
.filter(transaction -> transaction.isNotReversed() && !transaction.isAccrual()
|
.collect(Collectors.toList());
|
||||||
&& (transaction.isChargeOff() || transaction.isReAge() || transaction.isReAmortize()
|
|
||||||
|| !transaction.isNonMonetaryTransaction()))
|
|
||||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<LoanTransaction> retrieveListOfTransactionsPostDisbursementExcludeAccruals() {
|
private static Predicate<LoanTransaction> loanTransactionForReprocessingPredicate() {
|
||||||
return this.loanTransactions.stream()
|
return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge()
|
||||||
.filter(transaction -> transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isRepaymentAtDisbursement()
|
|| transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction());
|
||||||
|| transaction.isNonMonetaryTransaction() || transaction.isIncomePosting()))
|
|
||||||
.sorted(LoanTransactionComparator.INSTANCE).collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
|
private List<LoanTransaction> retrieveListOfTransactionsExcludeAccruals() {
|
||||||
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
final List<LoanTransaction> repaymentsOrWaivers = new ArrayList<>();
|
||||||
for (final LoanTransaction transaction : this.loanTransactions) {
|
for (final LoanTransaction transaction : this.loanTransactions) {
|
||||||
if (transaction.isNotReversed() && !(transaction.isAccrual() || transaction.isNonMonetaryTransaction())) {
|
if (transaction.isNotReversed() && !transaction.isNonMonetaryTransaction()) {
|
||||||
repaymentsOrWaivers.add(transaction);
|
repaymentsOrWaivers.add(transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3901,6 +3896,13 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
return currentTransactionDate;
|
return currentTransactionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LoanTransaction getLastTransactionForReprocessing() {
|
||||||
|
return loanTransactions.stream() //
|
||||||
|
.filter(Loan.loanTransactionForReprocessingPredicate()) //
|
||||||
|
.reduce((first, second) -> second) //
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
public LoanTransaction getLastPaymentTransaction() {
|
public LoanTransaction getLastPaymentTransaction() {
|
||||||
return loanTransactions.stream() //
|
return loanTransactions.stream() //
|
||||||
.filter(loanTransaction -> !loanTransaction.isReversed()) //
|
.filter(loanTransaction -> !loanTransaction.isReversed()) //
|
||||||
@@ -4526,7 +4528,7 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
}
|
}
|
||||||
outstanding = outstanding.plus(transactionOutstanding);
|
outstanding = outstanding.plus(transactionOutstanding);
|
||||||
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
loanTransaction.updateOutstandingLoanBalance(MathUtil.negativeToZero(outstanding.getAmount()));
|
||||||
} else {
|
} else if (!loanTransaction.isAccrualActivity()) {
|
||||||
if (this.loanInterestRecalculationDetails != null
|
if (this.loanInterestRecalculationDetails != null
|
||||||
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
|
&& this.loanInterestRecalculationDetails.isCompoundingToBePostedAsTransaction()
|
||||||
&& !loanTransaction.isRepaymentAtDisbursement()) {
|
&& !loanTransaction.isRepaymentAtDisbursement()) {
|
||||||
|
|||||||
+8
-8
@@ -879,11 +879,11 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
|
|
||||||
public boolean isNonMonetaryTransaction() {
|
public boolean isNonMonetaryTransaction() {
|
||||||
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|
return isNotReversed() && (LoanTransactionType.CONTRA.equals(getTypeOf())
|
||||||
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf())
|
|| LoanTransactionType.MARKED_FOR_RESCHEDULING.equals(getTypeOf()) || LoanTransactionType.ACCRUAL.equals(getTypeOf())
|
||||||
|| LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf())
|
|| LoanTransactionType.ACCRUAL_ACTIVITY.equals(getTypeOf()) || LoanTransactionType.APPROVE_TRANSFER.equals(getTypeOf())
|
||||||
|| LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf()) || LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf())
|
|| LoanTransactionType.INITIATE_TRANSFER.equals(getTypeOf()) || LoanTransactionType.REJECT_TRANSFER.equals(getTypeOf())
|
||||||
|| LoanTransactionType.CHARGE_OFF.equals(getTypeOf()) || LoanTransactionType.REAMORTIZE.equals(getTypeOf())
|
|| LoanTransactionType.WITHDRAW_TRANSFER.equals(getTypeOf()) || LoanTransactionType.CHARGE_OFF.equals(getTypeOf())
|
||||||
|| LoanTransactionType.REAGE.equals(getTypeOf()));
|
|| LoanTransactionType.REAMORTIZE.equals(getTypeOf()) || LoanTransactionType.REAGE.equals(getTypeOf()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
public void updateOutstandingLoanBalance(BigDecimal outstandingLoanBalance) {
|
||||||
@@ -949,7 +949,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
public Boolean isAllowTypeTransactionAtTheTimeOfLastUndo() {
|
||||||
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement() || isRepayment();
|
return isDisbursement() || isAccrual() || isRepaymentAtDisbursement() || isRepayment() || isAccrualActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isAccrualTransaction() {
|
public boolean isAccrualTransaction() {
|
||||||
@@ -969,8 +969,8 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPaymentTransaction() {
|
public boolean isPaymentTransaction() {
|
||||||
return this.isNotReversed() && !(this.isDisbursement() || this.isAccrual() || this.isRepaymentAtDisbursement()
|
return this.isNotReversed() && !(this.isDisbursement() || this.isRepaymentAtDisbursement() || this.isNonMonetaryTransaction()
|
||||||
|| this.isNonMonetaryTransaction() || this.isIncomePosting());
|
|| this.isIncomePosting());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<LoanCollateralManagement> getLoanCollateralManagementSet() {
|
public Set<LoanCollateralManagement> getLoanCollateralManagementSet() {
|
||||||
|
|||||||
+31
@@ -204,12 +204,43 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
||||||
} else if (loanTransaction.isChargeOff()) {
|
} else if (loanTransaction.isChargeOff()) {
|
||||||
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
||||||
|
} else if (loanTransaction.isAccrualActivity()) {
|
||||||
|
recalculateAccrualActivityTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reprocessInstallments(disbursementDate, transactionsToBeProcessed, installments, currency);
|
reprocessInstallments(disbursementDate, transactionsToBeProcessed, installments, currency);
|
||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||||
|
List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
loanTransaction.resetDerivedComponents();
|
||||||
|
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||||
|
final Money principalPortion = Money.zero(currency);
|
||||||
|
Money interestPortion = Money.zero(currency);
|
||||||
|
Money feeChargesPortion = Money.zero(currency);
|
||||||
|
Money penaltychargesPortion = Money.zero(currency);
|
||||||
|
for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
|
||||||
|
if (loanTransaction.getDateOf().isEqual(currentInstallment.getDueDate())) {
|
||||||
|
interestPortion = interestPortion.plus(currentInstallment.getInterestCharged(currency));
|
||||||
|
feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesCharged(currency));
|
||||||
|
penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recalculateAccrualActivityTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
|
||||||
|
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
|
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
||||||
|
|
||||||
|
calculateAccrualActivity(newLoanTransaction, currency, installments);
|
||||||
|
|
||||||
|
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
||||||
|
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
public void processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
||||||
switch (loanTransaction.getTypeOf()) {
|
switch (loanTransaction.getTypeOf()) {
|
||||||
|
|||||||
+1
-1
@@ -2785,7 +2785,7 @@ public abstract class AbstractCumulativeLoanScheduleGenerator implements LoanSch
|
|||||||
|
|
||||||
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
|
LoanScheduleDTO loanScheduleDTO = rescheduleNextInstallments(mc, loanApplicationTerms, loan, holidayDetailDTO,
|
||||||
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
loanRepaymentScheduleTransactionProcessor, onDate, calculateTill);
|
||||||
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsPostDisbursementExcludeAccruals();
|
List<LoanTransaction> loanTransactions = loan.retrieveListOfTransactionsForReprocessing();
|
||||||
|
|
||||||
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
loanRepaymentScheduleTransactionProcessor.reprocessLoanTransactions(loanApplicationTerms.getExpectedDisbursementDate(),
|
||||||
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
loanTransactions, currency, loanScheduleDTO.getInstallments(), loan.getActiveCharges());
|
||||||
|
|||||||
+1
@@ -197,6 +197,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
||||||
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
||||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||||
|
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx.getCurrency(), ctx.getInstallments());
|
||||||
// TODO: Cover rest of the transaction types
|
// TODO: Cover rest of the transaction types
|
||||||
default -> {
|
default -> {
|
||||||
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
log.warn("Unhandled transaction processing for transaction type: {}", loanTransaction.getTypeOf());
|
||||||
|
|||||||
+10
@@ -57,6 +57,10 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAccrualActivity() {
|
||||||
|
return loanTransaction.isPresent() && loanTransaction.get().isAccrualActivity();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isBackdatedCharge() {
|
private boolean isBackdatedCharge() {
|
||||||
return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate());
|
return loanCharge.get().getDueDate().isBefore(loanCharge.get().getSubmittedOnDate());
|
||||||
}
|
}
|
||||||
@@ -86,6 +90,12 @@ public class ChargeOrTransaction implements Comparable<ChargeOrTransaction> {
|
|||||||
public int compareTo(@NotNull ChargeOrTransaction o) {
|
public int compareTo(@NotNull ChargeOrTransaction o) {
|
||||||
int datePortion = this.getEffectiveDate().compareTo(o.getEffectiveDate());
|
int datePortion = this.getEffectiveDate().compareTo(o.getEffectiveDate());
|
||||||
if (datePortion == 0) {
|
if (datePortion == 0) {
|
||||||
|
if (this.isAccrualActivity() && !o.isAccrualActivity()) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!this.isAccrualActivity() && o.isAccrualActivity()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
int submittedDate = this.getSubmittedOnDate().compareTo(o.getSubmittedOnDate());
|
int submittedDate = this.getSubmittedOnDate().compareTo(o.getSubmittedOnDate());
|
||||||
if (submittedDate == 0) {
|
if (submittedDate == 0) {
|
||||||
return this.getCreatedDateTime().compareTo(o.getCreatedDateTime());
|
return this.getCreatedDateTime().compareTo(o.getCreatedDateTime());
|
||||||
|
|||||||
+1
-1
@@ -264,7 +264,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
|
|||||||
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
|
// [For Adv payment allocation strategy] check if charge due date is earlier than last transaction
|
||||||
// date, if yes trigger reprocess else no reprocessing
|
// date, if yes trigger reprocess else no reprocessing
|
||||||
if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(loan.transactionProcessingStrategy())) {
|
if (AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(loan.transactionProcessingStrategy())) {
|
||||||
LoanTransaction lastPaymentTransaction = loan.getLastPaymentTransaction();
|
LoanTransaction lastPaymentTransaction = loan.getLastTransactionForReprocessing();
|
||||||
if (lastPaymentTransaction != null) {
|
if (lastPaymentTransaction != null) {
|
||||||
if (loanCharge.getEffectiveDueDate() != null
|
if (loanCharge.getEffectiveDueDate() != null
|
||||||
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransaction.getTransactionDate())) {
|
&& DateUtils.isAfter(loanCharge.getEffectiveDueDate(), lastPaymentTransaction.getTransactionDate())) {
|
||||||
|
|||||||
+332
-2
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
|
||||||
import static org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder.DEFAULT_STRATEGY;
|
import static org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder.DEFAULT_STRATEGY;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
@@ -27,9 +28,14 @@ import io.restassured.http.ContentType;
|
|||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.apache.fineract.client.models.AdvancedPaymentData;
|
||||||
|
import org.apache.fineract.client.models.PaymentAllocationOrder;
|
||||||
import org.apache.fineract.client.models.PostChargesRequest;
|
import org.apache.fineract.client.models.PostChargesRequest;
|
||||||
import org.apache.fineract.client.models.PostChargesResponse;
|
import org.apache.fineract.client.models.PostChargesResponse;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
@@ -48,9 +54,13 @@ import org.apache.fineract.integrationtests.inlinecob.InlineLoanCOBHelper;
|
|||||||
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeCalculationType;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
import org.apache.fineract.portfolio.charge.domain.ChargePaymentMode;
|
||||||
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
import org.apache.fineract.portfolio.charge.domain.ChargeTimeType;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
|
import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.CsvSource;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
@@ -92,8 +102,13 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
// charge fee with due date as 1st installment
|
// charge fee with due date as 1st installment
|
||||||
// charge penalty with due date as 3rd installment
|
// charge penalty with due date as 3rd installment
|
||||||
// charge fee with due date as 2nd installment
|
// charge fee with due date as 2nd installment
|
||||||
|
// set business day to the day before closing day of 1st installment, run COB for loan, verify no Accrual Activity
|
||||||
|
// posted
|
||||||
|
// set business day to the closing day of 1st installment, run COB for loan, verify Accrual Activity posted
|
||||||
|
// set business day to the day after closing day of 1st installment, run COB for loan, verify no Accrual Activity
|
||||||
|
// posted
|
||||||
@Test
|
@Test
|
||||||
public void test() {
|
public void testAccrualActivityPosting() {
|
||||||
final String disbursementDay = "01 January 2023";
|
final String disbursementDay = "01 January 2023";
|
||||||
final String repaymentPeriod1DueDate = "01 February 2023";
|
final String repaymentPeriod1DueDate = "01 February 2023";
|
||||||
final String repaymentPeriod1CloseDate = "02 February 2023";
|
final String repaymentPeriod1CloseDate = "02 February 2023";
|
||||||
@@ -146,13 +161,250 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
transaction(19.35, "Accrual", "31 January 2023", 0, 0, 19.35, 0, 0, 0.0, 0.0),
|
transaction(19.35, "Accrual", "31 January 2023", 0, 0, 19.35, 0, 0, 0.0, 0.0),
|
||||||
transaction(70.65, "Accrual", "01 February 2023", 0, 0, 0.65, 40, 30, 0.0, 0.0),
|
transaction(70.65, "Accrual", "01 February 2023", 0, 0, 0.65, 40, 30, 0.0, 0.0),
|
||||||
transaction(90.0, "Accrual Activity", "01 February 2023", 1000, 0, 20.0, 40.0, 30.0, 0.0, 0.0),
|
transaction(90.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 40.0, 30.0, 0.0, 0.0),
|
||||||
transaction(0.71, "Accrual", "02 February 2023", 0, 0, 0.71, 0, 0, 0.0, 0.0));
|
transaction(0.71, "Accrual", "02 February 2023", 0, 0, 0.71, 0, 0, 0.0, 0.0));
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create Loan with Interest and enabled Accrual Activity Posting
|
||||||
|
// Approve and disburse loan
|
||||||
|
// make partial repayment before first installment day
|
||||||
|
// run COB on closing day of first installment
|
||||||
|
// verify that the Accrual Activity transaction is created
|
||||||
|
// make repayment before the first repayment
|
||||||
|
// verify that Accrual Activity transaction is NOT modified/reversed/replayed
|
||||||
|
// charge backdated penalty before first installment due date
|
||||||
|
// verify that the Accrual Activity transaction is reverse replayed
|
||||||
|
// verify that the Accrual Activity holds the correct portions
|
||||||
|
// charge backdated penalty before first installment due date
|
||||||
|
// verify that the Accrual Activity transaction is reverse replayed
|
||||||
|
// verify that the Accrual Activity holds the correct portions
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAccrualActivityPostingReverseReplay() {
|
||||||
|
final String disbursementDay = "01 January 2023";
|
||||||
|
final String repaymentPeriod1CloseDate = "02 February 2023";
|
||||||
|
final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023";
|
||||||
|
final String creationBusinessDay = "15 May 2023";
|
||||||
|
AtomicReference<Long> loanId = new AtomicReference<>();
|
||||||
|
runAt(creationBusinessDay, () -> {
|
||||||
|
|
||||||
|
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicWithInterest();
|
||||||
|
loanId.set(applyForLoanApplicationWithInterest(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
|
||||||
|
disbursementDay));
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
|
||||||
|
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay)
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
addRepaymentForLoan(loanId.get(), 50.0, "10 January 2023");
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 970, 30, 20, 0, 0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1CloseDate, () -> {
|
||||||
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 970, 30, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 0.0, 0.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1OneDayAfterCloseDate, () -> {
|
||||||
|
|
||||||
|
addRepaymentForLoan(loanId.get(), 200.0, "8 January 2023");
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 770, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(200.0, "Repayment", "08 January 2023", 820, 180, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 0.0, 0.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
chargePenalty(loanId.get(), 33.0, "01 February 2023");
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 803, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(200.0, "Repayment", "08 January 2023", 853, 147, 20, 0, 33, 0.0, 0.0),
|
||||||
|
transaction(53.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 0.0, 33.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
chargeFee(loanId.get(), 12.0, "01 February 2023");
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 20, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 815, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(200.0, "Repayment", "08 January 2023", 865, 135, 20, 12, 33, 0.0, 0.0),
|
||||||
|
transaction(65.0, "Accrual Activity", "01 February 2023", 0, 0, 20.0, 12.0, 33.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Loan with Advanced Payment Allocation and enabled Accrual Activity Posting
|
||||||
|
// Approve and disburse loan
|
||||||
|
// charge penalty for 1st installment
|
||||||
|
// make partial repayment before first installment day
|
||||||
|
// run COB on closing day of first installment
|
||||||
|
// verify that the Accrual Activity transaction is created
|
||||||
|
// make repayment before the first repayment
|
||||||
|
// verify that Accrual Activity transaction is NOT modified/reversed/replayed
|
||||||
|
// charge backdated penalty before first installment due date
|
||||||
|
// verify that the Accrual Activity transaction is reverse replayed
|
||||||
|
// verify that the Accrual Activity holds the correct portions
|
||||||
|
// charge backdated penalty before first installment due date
|
||||||
|
// verify that the Accrual Activity transaction is reverse replayed
|
||||||
|
// verify that the Accrual Activity holds the correct portions
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({ "29 January 2023,30 January 2023,31 January 2023", "31 January 2023,30 January 2023,29 January 2023",
|
||||||
|
"31 January 2023,31 January 2023,31 January 2023", "01 February 2023,01 February 2023,01 February 2023" })
|
||||||
|
public void testAccrualActivityPostingReverseReplayAdvancedPaymentAllocation(final String chargeDueDate1st,
|
||||||
|
final String chargeDueDate2st, final String chargeDueDate3st) {
|
||||||
|
final String disbursementDay = "01 January 2023";
|
||||||
|
final String repaymentPeriod1CloseDate = "02 February 2023";
|
||||||
|
final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023";
|
||||||
|
final String creationBusinessDay = "15 May 2023";
|
||||||
|
AtomicReference<Long> loanId = new AtomicReference<>();
|
||||||
|
runAt(creationBusinessDay, () -> {
|
||||||
|
|
||||||
|
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
|
||||||
|
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
|
||||||
|
disbursementDay));
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
|
||||||
|
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay)
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId);
|
||||||
|
LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId());
|
||||||
|
LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId);
|
||||||
|
|
||||||
|
chargePenalty(loanId.get(), 20.0, chargeDueDate1st);
|
||||||
|
|
||||||
|
addRepaymentForLoan(loanId.get(), 50.0, "10 January 2023");
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 970, 30, 0, 0, 20, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1CloseDate, () -> {
|
||||||
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 950, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1OneDayAfterCloseDate, () -> {
|
||||||
|
|
||||||
|
addRepaymentForLoan(loanId.get(), 220.0, "8 January 2023");
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
chargePenalty(loanId.get(), 33.0, chargeDueDate2st);
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(53.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 53.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
chargeFee(loanId.get(), 12.0, chargeDueDate3st);
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(50.0, "Repayment", "10 January 2023", 730, 50, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(220.0, "Repayment", "08 January 2023", 780, 220, 0, 0, 0, 0.0, 0.0),
|
||||||
|
transaction(65.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 12.0, 53.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Loan with Advanced Payment Allocation and enabled Accrual Activity Posting
|
||||||
|
// Approve and disburse loan
|
||||||
|
// charge penalty for 1st installment
|
||||||
|
// run COB on closing day of first installment
|
||||||
|
// verify that the Accrual Activity transaction is created
|
||||||
|
// charge backdated fee before first installment due date
|
||||||
|
// verify that the Accrual Activity transaction is reverse replayed
|
||||||
|
// verify that the Accrual Activity holds the correct portions
|
||||||
|
@ParameterizedTest
|
||||||
|
@CsvSource({ "29 January 2023,30 January 2023,31 January 2023", "31 January 2023,30 January 2023,29 January 2023",
|
||||||
|
"31 January 2023,31 January 2023,31 January 2023", "01 February 2023,01 February 2023,01 February 2023" })
|
||||||
|
public void testAccrualActivityPostingReverseReplayAdvancedPaymentAllocationBasicFlow(final String chargeDueDate1st,
|
||||||
|
final String chargeDueDate2st, final String chargeDueDate3st) {
|
||||||
|
final String disbursementDay = "01 January 2023";
|
||||||
|
final String repaymentPeriod1CloseDate = "02 February 2023";
|
||||||
|
final String repaymentPeriod1OneDayAfterCloseDate = "03 February 2023";
|
||||||
|
final String creationBusinessDay = "15 May 2023";
|
||||||
|
AtomicReference<Long> loanId = new AtomicReference<>();
|
||||||
|
runAt(creationBusinessDay, () -> {
|
||||||
|
|
||||||
|
Long localLoanProductId = createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation();
|
||||||
|
loanId.set(applyForLoanApplicationAdvancedPaymentAllocation(client.getClientId(), localLoanProductId, BigDecimal.valueOf(40000),
|
||||||
|
disbursementDay));
|
||||||
|
|
||||||
|
loanTransactionHelper.approveLoan(loanId.get(), new PostLoansLoanIdRequest().approvedLoanAmount(BigDecimal.valueOf(1000))
|
||||||
|
.dateFormat(DATETIME_PATTERN).approvedOnDate(disbursementDay).locale("en"));
|
||||||
|
|
||||||
|
loanTransactionHelper.disburseLoan(loanId.get(), new PostLoansLoanIdRequest().actualDisbursementDate(disbursementDay)
|
||||||
|
.dateFormat(DATETIME_PATTERN).transactionAmount(BigDecimal.valueOf(1000.0)).locale("en"));
|
||||||
|
|
||||||
|
LOG.info("Test Loan Product Id {1} http://localhost:4200/#/products/loan-products/{}/general", localLoanProductId);
|
||||||
|
LOG.info("Test Client Id {1} http://localhost:4200/#/clients/{}", client.getClientId());
|
||||||
|
LOG.info("Test Loan Id {2} http://localhost:4200/#/clients/{}/loans-accounts/{}/transactions", client.getClientId(), loanId);
|
||||||
|
|
||||||
|
chargePenalty(loanId.get(), 20.0, chargeDueDate1st);
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1CloseDate, () -> {
|
||||||
|
inlineLoanCOBHelper.executeInlineCOB(List.of(loanId.get()));
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
runAt(repaymentPeriod1OneDayAfterCloseDate, () -> {
|
||||||
|
chargePenalty(loanId.get(), 33.0, chargeDueDate2st);
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(53.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 0.0, 53.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
chargeFee(loanId.get(), 12.0, chargeDueDate3st);
|
||||||
|
|
||||||
|
verifyTransactions(loanId.get(), //
|
||||||
|
transaction(1000.0, "Disbursement", disbursementDay, 1000.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "01 February 2023", 0, 0, 0, 0, 20, 0.0, 0.0),
|
||||||
|
transaction(65.0, "Accrual Activity", "01 February 2023", 0, 0, 0.0, 12.0, 53.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void chargeFee(Long loanId, Double amount, String dueDate) {
|
private void chargeFee(Long loanId, Double amount, String dueDate) {
|
||||||
|
LOG.info("----------------------------------CHARGE FEE---------------------------------");
|
||||||
|
LOG.info("FEE amount {} dueDate {}", amount, dueDate);
|
||||||
PostChargesResponse feeCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(false).amount(9.0)
|
PostChargesResponse feeCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(false).amount(9.0)
|
||||||
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
|
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
|
||||||
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
|
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
|
||||||
@@ -166,6 +418,8 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void chargePenalty(Long loanId, Double amount, String dueDate) {
|
private void chargePenalty(Long loanId, Double amount, String dueDate) {
|
||||||
|
LOG.info("----------------------------------CHARGE PENALTY---------------------------------");
|
||||||
|
LOG.info("PENALTY amount {} dueDate {}", amount, dueDate);
|
||||||
PostChargesResponse penaltyCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(true).amount(10.0)
|
PostChargesResponse penaltyCharge = chargesHelper.createCharges(new PostChargesRequest().penalty(true).amount(10.0)
|
||||||
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
|
.chargeCalculationType(ChargeCalculationType.FLAT.getValue()).chargeTimeType(ChargeTimeType.SPECIFIED_DUE_DATE.getValue())
|
||||||
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
|
.chargePaymentMode(ChargePaymentMode.REGULAR.getValue()).currencyCode("USD")
|
||||||
@@ -176,6 +430,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
.amount(amount).dueDate(dueDate));
|
.amount(amount).dueDate(dueDate));
|
||||||
assertNotNull(penaltyLoanChargeResult);
|
assertNotNull(penaltyLoanChargeResult);
|
||||||
assertNotNull(penaltyLoanChargeResult.getResourceId());
|
assertNotNull(penaltyLoanChargeResult.getResourceId());
|
||||||
|
LOG.info("----------------------------------CHARGE PENALTY---------------------------------");
|
||||||
}
|
}
|
||||||
|
|
||||||
private Long createLoanProductAccountingAccrualPeriodicWithInterest() {
|
private Long createLoanProductAccountingAccrualPeriodicWithInterest() {
|
||||||
@@ -225,4 +480,79 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati
|
|||||||
return loanTransactionHelper.applyLoan(loanRequest).getLoanId();
|
return loanTransactionHelper.applyLoan(loanRequest).getLoanId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Long createLoanProductAccountingAccrualPeriodicAdvancedPaymentAllocation() {
|
||||||
|
LOG.info("------------------------------CREATING NEW LOAN PRODUCT ---------------------------------------");
|
||||||
|
String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6);
|
||||||
|
String shortName = Utils.uniqueRandomStringGenerator("", 4);
|
||||||
|
AdvancedPaymentData defaultAllocation = createDefaultPaymentAllocation();
|
||||||
|
return loanTransactionHelper.createLoanProduct(new PostLoanProductsRequest().name(name).shortName(shortName)
|
||||||
|
.description("Test loan description").currencyCode("USD").digitsAfterDecimal(2).daysInYearType(1).daysInMonthType(1)
|
||||||
|
.recalculationRestFrequencyType(1).rescheduleStrategyMethod(1).loanScheduleType(LoanScheduleType.PROGRESSIVE.name())
|
||||||
|
.recalculationRestFrequencyInterval(0).isInterestRecalculationEnabled(false).locale("en_GB").numberOfRepayments(4)
|
||||||
|
.repaymentFrequencyType(2L).repaymentEvery(1).minPrincipal(100.0).principal(1000.0).maxPrincipal(10000000.0)
|
||||||
|
.amortizationType(1).interestType(0).interestRatePerPeriod(0.0).interestRateFrequencyType(1)
|
||||||
|
.interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).paymentAllocation(List.of(defaultAllocation))
|
||||||
|
.accountingRule(3).enableAccrualActivityPosting(true).fundSourceAccountId(fundSource.getAccountID().longValue())//
|
||||||
|
.loanPortfolioAccountId(loansReceivableAccount.getAccountID().longValue())//
|
||||||
|
.transfersInSuspenseAccountId(suspenseAccount.getAccountID().longValue())//
|
||||||
|
.interestOnLoanAccountId(interestIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromFeeAccountId(feeIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromPenaltyAccountId(feeIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromRecoveryAccountId(recoveriesAccount.getAccountID().longValue())//
|
||||||
|
.writeOffAccountId(writtenOffAccount.getAccountID().longValue())//
|
||||||
|
.overpaymentLiabilityAccountId(overpaymentAccount.getAccountID().longValue())//
|
||||||
|
.receivableInterestAccountId(interestReceivableAccount.getAccountID().longValue())//
|
||||||
|
.receivableFeeAccountId(feeReceivableAccount.getAccountID().longValue())//
|
||||||
|
.receivablePenaltyAccountId(penaltyReceivableAccount.getAccountID().longValue())//
|
||||||
|
.goodwillCreditAccountId(goodwillExpenseAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditInterestAccountId(goodwillIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditFeesAccountId(goodwillIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromGoodwillCreditPenaltyAccountId(goodwillIncomeAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffInterestAccountId(interestIncomeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffFeesAccountId(feeChargeOffAccount.getAccountID().longValue())//
|
||||||
|
.chargeOffExpenseAccountId(chargeOffExpenseAccount.getAccountID().longValue())//
|
||||||
|
.chargeOffFraudExpenseAccountId(chargeOffFraudExpenseAccount.getAccountID().longValue())//
|
||||||
|
.incomeFromChargeOffPenaltyAccountId(penaltyChargeOffAccount.getAccountID().longValue())//
|
||||||
|
).getResourceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static 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 static Long applyForLoanApplicationAdvancedPaymentAllocation(final Long clientID, final Long loanProductID,
|
||||||
|
BigDecimal principal, String applicationDisbursementDate) {
|
||||||
|
LOG.info("--------------------------------APPLYING FOR LOAN APPLICATION--------------------------------");
|
||||||
|
final PostLoansRequest loanRequest = new PostLoansRequest() //
|
||||||
|
.loanTermFrequency(4).locale("en_GB").loanTermFrequencyType(2).numberOfRepayments(4).repaymentFrequencyType(2)
|
||||||
|
.repaymentEvery(1).principal(principal).amortizationType(1).interestType(0).interestRatePerPeriod(BigDecimal.ZERO)
|
||||||
|
.interestCalculationPeriodType(1).dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION_STRATEGY).loanType("individual")
|
||||||
|
.expectedDisbursementDate(applicationDisbursementDate).submittedOnDate(applicationDisbursementDate).clientId(clientID)
|
||||||
|
.productId(loanProductID);
|
||||||
|
return loanTransactionHelper.applyLoan(loanRequest).getLoanId();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user