From 4c503fff0de67dc45e0fe2d1b1d3ac435a879683 Mon Sep 17 00:00:00 2001 From: Adam Saghy Date: Fri, 5 Apr 2024 19:23:51 +0200 Subject: [PATCH] FINERACT-2060: Fix balance of reamortization at the event of reverse-replay --- .../features/LoanReAmortization.feature | 2 +- ...edPaymentScheduleTransactionProcessor.java | 15 +- .../loans/LoanTestLifecycleExtension.java | 2 +- .../LoanReAmortizationIntegrationTest.java | 435 ++++++++++++------ 4 files changed, 291 insertions(+), 163 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature index 2f2c50191..c0bb30b4e 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAmortization.feature @@ -250,7 +250,7 @@ Feature: LoanReAmortization | 01 January 2024 | Disbursement | 500.0 | 0.0 | 0.0 | 0.0 | 0.0 | 500.0 | | 01 January 2024 | Down Payment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 375.0 | | 15 January 2024 | Repayment | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 250.0 | - | 01 February 2024 | Re-amortize | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 0.0 | + | 01 February 2024 | Re-amortize | 125.0 | 125.0 | 0.0 | 0.0 | 0.0 | 0.0 | @TestRailId:C3076 @AdvancedPaymentAllocation Scenario: Verify Loan re-amortization transaction - UC4: N+1 Installment scenario diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 37b920325..e1f12c30d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -206,7 +206,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep private void handleReAmortization(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments) { - BigDecimal remainingAmount = loanTransaction.getAmount(); LocalDate transactionDate = loanTransaction.getTransactionDate(); List previousInstallments = installments.stream() // .filter(installment -> !installment.getDueDate().isAfter(transactionDate)) // @@ -224,28 +223,26 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep installment.updateDerivedFields(currency, transactionDate); } - if (overallOverDuePrincipal.compareTo(remainingAmount) != 0) { - remainingAmount = overallOverDuePrincipal; - loanTransaction.updateComponentsAndTotal(Money.of(currency, remainingAmount), Money.zero(currency), Money.zero(currency), - Money.zero(currency)); - } + loanTransaction.resetDerivedComponents(); + loanTransaction.updateComponentsAndTotal(Money.of(currency, overallOverDuePrincipal), Money.zero(currency), Money.zero(currency), + Money.zero(currency)); LoanRepaymentScheduleInstallment lastFutureInstallment = futureInstallments.stream() .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getDueDate)).get(); - BigDecimal reAmortizationAmountPerInstallment = remainingAmount.divide(BigDecimal.valueOf(futureInstallments.size()), + BigDecimal reAmortizationAmountPerInstallment = overallOverDuePrincipal.divide(BigDecimal.valueOf(futureInstallments.size()), MoneyHelper.getRoundingMode()); Integer installmentAmountInMultiplesOf = loanTransaction.getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf(); for (LoanRepaymentScheduleInstallment installment : futureInstallments) { if (lastFutureInstallment.equals(installment)) { - installment.addToPrincipal(transactionDate, Money.of(currency, remainingAmount)); + installment.addToPrincipal(transactionDate, Money.of(currency, overallOverDuePrincipal)); } else { if (installmentAmountInMultiplesOf != null) { reAmortizationAmountPerInstallment = Money.roundToMultiplesOf(reAmortizationAmountPerInstallment, installmentAmountInMultiplesOf); } installment.addToPrincipal(transactionDate, Money.of(currency, reAmortizationAmountPerInstallment)); - remainingAmount = remainingAmount.subtract(reAmortizationAmountPerInstallment); + overallOverDuePrincipal = overallOverDuePrincipal.subtract(reAmortizationAmountPerInstallment); } } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTestLifecycleExtension.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTestLifecycleExtension.java index ffe553ad6..e3d9b0f16 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTestLifecycleExtension.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTestLifecycleExtension.java @@ -43,7 +43,7 @@ public class LoanTestLifecycleExtension implements AfterEachCallback { private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter(); @Override - public void afterEach(ExtensionContext context) throws Exception { + public void afterEach(ExtensionContext context) { this.requestSpec = new RequestSpecBuilder().setContentType(ContentType.JSON).build(); this.requestSpec.header("Authorization", "Basic " + Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey()); this.responseSpec = new ResponseSpecBuilder().expectStatusCode(200).build(); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java index eae605740..bb5d7a877 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/loan/reamortization/LoanReAmortizationIntegrationTest.java @@ -25,7 +25,6 @@ import java.util.concurrent.atomic.AtomicLong; import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; import org.apache.fineract.client.models.PostLoanProductsRequest; import org.apache.fineract.client.models.PostLoanProductsResponse; -import org.apache.fineract.client.models.PostLoansLoanIdResponse; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.integrationtests.BaseLoanIntegrationTest; @@ -41,11 +40,10 @@ import org.junit.jupiter.api.extension.ExtendWith; public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25); + private final AtomicLong loanId = new AtomicLong(); @Test public void test_LoanReAmortizeTransaction_Works() { - AtomicLong createdLoanId = new AtomicLong(); - runAt("01 January 2023", () -> { // Create Client Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); @@ -73,43 +71,37 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { .loanTermFrequencyType(RepaymentFrequencyType.MONTHS); PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + loanId.set(postLoansResponse.getLoanId()); - PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount, "01 January 2023")); - - Long loanId = approvedLoanResult.getLoanId(); + loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(amount, "01 January 2023")); // disburse Loan - disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(1250.0), "01 January 2023"); // verify transactions - verifyTransactions(loanId, // + verifyTransactions(loanId.get(), // transaction(1250.0, "Disbursement", "01 January 2023") // ); // verify schedule - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(625.0, false, "01 February 2023"), // installment(625.0, false, "01 March 2023") // ); - - createdLoanId.set(loanId); }); runAt("02 February 2023", () -> { - long loanId = createdLoanId.get(); - // create re-amortize transaction - reAmortizeLoan(loanId); + reAmortizeLoan(loanId.get()); // verify transactions - verifyTransactions(loanId, // + verifyTransactions(loanId.get(), // transaction(1250.0, "Disbursement", "01 January 2023"), // transaction(625.0, "Re-amortize", "02 February 2023") // ); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(0.0, true, "01 February 2023"), // installment(1250.0, false, "01 March 2023") // @@ -119,8 +111,6 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { @Test public void test_LoanUndoReAmortizeTransaction_Works() { - AtomicLong createdLoanId = new AtomicLong(); - runAt("01 January 2023", () -> { // Create Client Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); @@ -148,56 +138,48 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { .loanTermFrequencyType(RepaymentFrequencyType.MONTHS); PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest); + loanId.set(postLoansResponse.getLoanId()); - PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), - approveLoanRequest(amount, "01 January 2023")); - - Long loanId = approvedLoanResult.getLoanId(); + loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(), approveLoanRequest(amount, "01 January 2023")); // disburse Loan - disburseLoan(loanId, BigDecimal.valueOf(1250.0), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(1250.0), "01 January 2023"); // verify transactions - verifyTransactions(loanId, // + verifyTransactions(loanId.get(), // transaction(1250.0, "Disbursement", "01 January 2023") // ); // verify schedule - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(625.0, false, "01 February 2023"), // installment(625.0, false, "01 March 2023") // ); - - createdLoanId.set(loanId); }); runAt("02 February 2023", () -> { - long loanId = createdLoanId.get(); - // create re-amortize transaction - reAmortizeLoan(loanId); + reAmortizeLoan(loanId.get()); // verify transactions - verifyTransactions(loanId, // + verifyTransactions(loanId.get(), // transaction(1250.0, "Disbursement", "01 January 2023"), // transaction(625.0, "Re-amortize", "02 February 2023") // ); }); runAt("03 February 2023", () -> { - long loanId = createdLoanId.get(); - // undo re-amortize transaction - undoReAmortizeLoan(loanId); + undoReAmortizeLoan(loanId.get()); // verify transactions - verifyTransactions(loanId, // + verifyTransactions(loanId.get(), // transaction(1250.0, "Disbursement", "01 January 2023"), // reversedTransaction(625.0, "Re-amortize", "02 February 2023") // ); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(625.0, false, "01 February 2023"), // installment(625.0, false, "01 March 2023") // @@ -211,17 +193,17 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // @@ -230,27 +212,33 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { ); updateBusinessDate("05 January 2023"); - addCharge(loanId, false, 10.0, "05 January 2023"); + addCharge(loanId.get(), false, 10.0, "05 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, 0.0, 10.0, 135.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("25 January 2023", () -> { + reAmortizeLoan(loanId.get()); - updateBusinessDate("25 January 2023"); - - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, 0.0, 10.0, 10.0, false, "16 January 2023"), // installment(187.5, false, "31 January 2023"), // installment(187.5, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Re-amortize", "25 January 2023") // + ); }); } @@ -260,35 +248,42 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("01 February 2023", () -> { - updateBusinessDate("01 February 2023"); + reAmortizeLoan(loanId.get()); - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, true, "16 January 2023"), // installment(0.0, true, "31 January 2023"), // installment(375.0, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(250.0, "Re-amortize", "01 February 2023") // + ); }); } @@ -298,38 +293,52 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("17 January 2023", () -> { + addRepaymentForLoan(loanId.get(), 50.0, "17 January 2023"); - updateBusinessDate("17 January 2023"); - addRepaymentForLoan(loanId, 50.0, "17 January 2023"); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(50.0, "Repayment", "17 January 2023") // + ); + }); + runAt("30 January 2023", () -> { + reAmortizeLoan(loanId.get()); - updateBusinessDate("30 January 2023"); - - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(50.0, true, "16 January 2023"), // installment(162.5, false, "31 January 2023"), // installment(162.5, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(50.0, "Repayment", "17 January 2023"), // + transaction(75.0, "Re-amortize", "30 January 2023") // + ); }); } @@ -339,46 +348,52 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("05 January 2023", () -> { + addCharge(loanId.get(), false, 10.0, "05 January 2023"); - updateBusinessDate("05 January 2023"); - addCharge(loanId, false, 10.0, "05 January 2023"); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, 0.0, 10.0, 135.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("31 January 2023", () -> { + reAmortizeLoan(loanId.get()); - updateBusinessDate("31 January 2023"); - - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, 0.0, 10.0, 10.0, false, "16 January 2023"), // installment(0.0, true, "31 January 2023"), // installment(375.0, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(250.0, "Re-amortize", "31 January 2023") // + ); }); } @@ -388,30 +403,30 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("01 February 2023", () -> { + addCharge(loanId.get(), false, 10.0, "27 February 2023"); - updateBusinessDate("01 February 2023"); - addCharge(loanId, false, 10.0, "27 February 2023"); + reAmortizeLoan(loanId.get()); - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, true, "16 January 2023"), // @@ -419,6 +434,13 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(375.0, false, "15 February 2023"), // installment(0.0, 0.0, 10.0, false, "27 February 2023") // ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(250.0, "Re-amortize", "01 February 2023") // + ); }); } @@ -428,38 +450,61 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("01 February 2023", () -> { + reAmortizeLoan(loanId.get()); - updateBusinessDate("01 February 2023"); + verifyRepaymentSchedule(loanId.get(), // + installment(0, null, "01 January 2023"), // + installment(125.0, true, "01 January 2023"), // + installment(0.0, true, "16 January 2023"), // + installment(0.0, true, "31 January 2023"), // + installment(375.0, false, "15 February 2023")// + ); - reAmortizeLoan(loanId); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(250.0, "Re-amortize", "01 February 2023") // + ); - updateBusinessDate("01 February 2023"); - addRepaymentForLoan(loanId, 125.0, "15 January 2023"); + }); + runAt("02 February 2023", () -> { + addRepaymentForLoan(loanId.get(), 125.0, "15 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, true, "16 January 2023"), // installment(0.0, true, "31 January 2023"), // installment(250.0, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Repayment", "15 January 2023"), // + transaction(125.0, "Re-amortize", "01 February 2023") // + ); }); } @@ -469,32 +514,47 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + AtomicLong repaymentTransactionId = new AtomicLong(); + runAt("15 January 2023", () -> { + repaymentTransactionId.set(addRepaymentForLoan(loanId.get(), 125.0, "15 January 2023")); - updateBusinessDate("15 January 2023"); - Long repaymentTransactionId = addRepaymentForLoan(loanId, 125.0, "15 January 2023"); + verifyRepaymentSchedule(loanId.get(), // + installment(0, null, "01 January 2023"), // + installment(125.0, true, "01 January 2023"), // + installment(125.0, true, "16 January 2023"), // + installment(125.0, false, "31 January 2023"), // + installment(125.0, false, "15 February 2023")// + ); - updateBusinessDate("01 February 2023"); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Repayment", "15 January 2023") // + ); + }); + runAt("01 February 2023", () -> { + reAmortizeLoan(loanId.get()); - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, true, "16 January 2023"), // @@ -502,15 +562,31 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(250.0, false, "15 February 2023")// ); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Repayment", "15 January 2023"), // + transaction(125.0, "Re-amortize", "01 February 2023") // + ); + loanTransactionHelper.reverseRepayment(loanId.intValue(), repaymentTransactionId.intValue(), "01 February 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, true, "16 January 2023"), // installment(0.0, true, "31 January 2023"), // installment(375.0, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Repayment", "15 January 2023"), // + transaction(250.0, "Re-amortize", "01 February 2023") // + ); }); } @@ -520,29 +596,29 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + }); + runAt("01 February 2023", () -> { - updateBusinessDate("01 February 2023"); + reAmortizeLoan(loanId.get()); - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, true, "16 January 2023"), // @@ -550,17 +626,31 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(375.0, false, "15 February 2023")// ); - updateBusinessDate("02 February 2023"); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(250.0, "Re-amortize", "01 February 2023") // + ); + }); + runAt("02 February 2023", () -> { - undoReAmortizeLoan(loanId); + undoReAmortizeLoan(loanId.get()); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + reversedTransaction(250.0, "Re-amortize", "01 February 2023") // + ); }); } @@ -570,17 +660,17 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(4, 15, BigDecimal.valueOf(20)); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 4, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 500.0, 4, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(60); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(100.0, true, "01 January 2023"), // installment(100.0, false, "16 January 2023"), // @@ -588,12 +678,11 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(100.0, false, "15 February 2023"), // installment(100.0, false, "02 March 2023")// ); + }); + runAt("17 January 2023", () -> { + reAmortizeLoan(loanId.get()); - updateBusinessDate("17 January 2023"); - - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(100.0, true, "01 January 2023"), // installment(0.0, true, "16 January 2023"), // @@ -601,6 +690,13 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(133.33, false, "15 February 2023"), // installment(133.34, false, "02 March 2023")// ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(100.0, "Down Payment", "01 January 2023"), // + transaction(100.0, "Re-amortize", "17 January 2023") // + ); }); } @@ -610,31 +706,32 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(3, 15); - Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 3, req -> { + loanId.set(applyAndApproveLoan(clientId, loanProductId, "01 January 2023", 1000.0, 3, req -> { req.setRepaymentEvery(15); req.setLoanTermFrequency(45); req.setTransactionProcessingStrategyCode("advanced-payment-allocation-strategy"); req.setLoanScheduleProcessingType(LoanScheduleType.PROGRESSIVE.toString()); req.setLoanScheduleProcessingType(LoanScheduleProcessingType.HORIZONTAL.toString()); - }); + })); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "01 January 2023"); + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "01 January 2023"); - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(125.0, false, "16 January 2023"), // installment(125.0, false, "31 January 2023"), // installment(125.0, false, "15 February 2023") // ); + }); + runAt("16 January 2023", () -> { + addCharge(loanId.get(), false, 10.0, "16 January 2023"); + }); + runAt("25 January 2023", () -> { - updateBusinessDate("16 January 2023"); - addCharge(loanId, false, 10.0, "16 January 2023"); + reAmortizeLoan(loanId.get()); - updateBusinessDate("25 January 2023"); - reAmortizeLoan(loanId); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, 0.0, 10.0, 10.0, false, "16 January 2023"), // @@ -642,11 +739,17 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(187.5, false, "15 February 2023") // ); - updateBusinessDate("26 January 2023"); + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Re-amortize", "25 January 2023") // + ); + }); + runAt("26 January 2023", () -> { + disburseLoan(loanId.get(), BigDecimal.valueOf(500.00), "26 January 2023"); - disburseLoan(loanId, BigDecimal.valueOf(500.00), "26 January 2023"); - - verifyRepaymentSchedule(loanId, // + verifyRepaymentSchedule(loanId.get(), // installment(0, null, "01 January 2023"), // installment(125.0, true, "01 January 2023"), // installment(0.0, 0.0, 10.0, 0.0, true, "16 January 2023"), // @@ -655,6 +758,42 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { installment(375.0, false, "31 January 2023"), // installment(375.0, false, "15 February 2023") // ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(125.0, "Re-amortize", "25 January 2023"), // + transaction(500.0, "Disbursement", "26 January 2023"), // + transaction(125.0, "Down Payment", "26 January 2023") // + ); + }); + + runAt("27 January 2023", () -> { + disburseLoan(loanId.get(), BigDecimal.valueOf(100.0), "10 January 2023"); + + verifyRepaymentSchedule(loanId.get(), // + installment(0, null, "01 January 2023"), // + installment(125.0, true, "01 January 2023"), // + installment(100.0, null, "10 January 2023"), // + installment(25.0, true, "10 January 2023"), // + installment(0.0, 0.0, 10.0, 0.0, true, "16 January 2023"), // + installment(500.0, null, "26 January 2023"), // + installment(125.0, false, "26 January 2023"), // + installment(412.5, false, "31 January 2023"), // + installment(412.5, false, "15 February 2023") // + ); + + // verify transactions + verifyTransactions(loanId.get(), // + transaction(500.0, "Disbursement", "01 January 2023"), // + transaction(125.0, "Down Payment", "01 January 2023"), // + transaction(100.0, "Disbursement", "10 January 2023"), // + transaction(25.0, "Down Payment", "10 January 2023"), // + transaction(150.0, "Re-amortize", "25 January 2023"), // + transaction(500.0, "Disbursement", "26 January 2023"), // + transaction(125.0, "Down Payment", "26 January 2023") // + ); }); } @@ -665,19 +804,11 @@ public class LoanReAmortizationIntegrationTest extends BaseLoanIntegrationTest { private Long createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(int numberOfInstallments, int repaymentEvery, BigDecimal downPaymentPercentage) { - boolean multiDisburseEnabled = true; PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation(); - product.setMultiDisburseLoan(multiDisburseEnabled); + product.setMultiDisburseLoan(true); product.setNumberOfRepayments(numberOfInstallments); product.setRepaymentEvery(repaymentEvery); - if (!multiDisburseEnabled) { - product.disallowExpectedDisbursements(null); - product.setAllowApprovedDisbursedAmountsOverApplied(null); - product.overAppliedCalculationType(null); - product.overAppliedNumber(null); - } - product.setEnableDownPayment(true); product.setDisbursedAmountPercentageForDownPayment(downPaymentPercentage); product.setEnableAutoRepaymentForDownPayment(true);