diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index 7226e6f23..fd42d7e3d 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -567,7 +567,7 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom { } public boolean isTypeAllowedForChargeback() { - return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit(); + return isRepayment() || isMerchantIssuedRefund() || isPayoutRefund() || isGoodwillCredit() || isDownPayment(); } public boolean isRepayment() { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDownPaymentTransactionChargebackTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDownPaymentTransactionChargebackTest.java new file mode 100644 index 000000000..17e05c2de --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanDownPaymentTransactionChargebackTest.java @@ -0,0 +1,206 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.fineract.integrationtests; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.math.BigDecimal; +import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; +import org.apache.fineract.client.models.GetLoanProductsProductIdResponse; +import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse; +import org.apache.fineract.client.models.PostLoanProductsRequest; +import org.apache.fineract.client.models.PostLoanProductsResponse; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; +import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType; +import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType; +import org.junit.jupiter.api.Test; + +public class LoanDownPaymentTransactionChargebackTest extends BaseLoanIntegrationTest { + + public static final BigDecimal DOWN_PAYMENT_PERCENTAGE = new BigDecimal(25); + + @Test + public void loanDownPaymentTransactionChargebackTest() { + runAt("03 March 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + // Create Loan Product + Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(false); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 March 2023", 1500.0, 3, req -> { + req.setRepaymentEvery(15); + req.setLoanTermFrequency(45); + }); + + // Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 March 2023"); + + // verify repayment schedule + verifyRepaymentSchedule(loanId, // + installment(0, null, "01 March 2023"), // + installment(250.0, false, "01 March 2023"), // + installment(250.0, false, "16 March 2023"), // + installment(250.0, false, "31 March 2023"), // + installment(250.0, false, "15 April 2023")// + ); + + // make down payment + final PostLoansLoanIdTransactionsResponse downPaymentTransaction_1 = loanTransactionHelper.makeLoanDownPayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("01 March 2023").locale("en") + .transactionAmount(250.0)); + assertNotNull(downPaymentTransaction_1); + + // chargeback down payment transaction + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId.intValue(), + downPaymentTransaction_1.getResourceId(), "50.00", 0, responseSpec); + + reviewLoanTransactionRelations(loanId.intValue(), downPaymentTransaction_1.getResourceId(), 1, Double.valueOf("750.00")); + reviewLoanTransactionRelations(loanId.intValue(), chargebackTransactionId, 0, Double.valueOf("800.00")); + + // verify repayment schedule + verifyRepaymentSchedule(loanId, // + installment(0, null, "01 March 2023"), // + installment(250.0, true, "01 March 2023"), // + installment(300.0, false, "16 March 2023"), // + installment(250.0, false, "31 March 2023"), // + installment(250.0, false, "15 April 2023")// + ); + + // verify journal entries for chargeback transaction + GetJournalEntriesTransactionIdResponse journalEntries = journalEntryHelper + .getJournalEntries("L" + chargebackTransactionId.toString()); + assertEquals(2L, journalEntries.getTotalFilteredRecords()); + assertEquals(50.0, journalEntries.getPageItems().get(0).getAmount()); + assertEquals("CREDIT", journalEntries.getPageItems().get(0).getEntryType().getValue()); + + assertEquals(50.0, journalEntries.getPageItems().get(1).getAmount()); + assertEquals("DEBIT", journalEntries.getPageItems().get(1).getEntryType().getValue()); + + }); + } + + @Test + public void loanDownPaymentTransactionChargebackForAdvancedPaymentAllocationTest() { + runAt("03 March 2023", () -> { + // Create Client + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + // Create Loan Product + Long loanProductId = createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(true); + + // Apply and Approve Loan + Long loanId = applyAndApproveLoan(clientId, loanProductId, "01 March 2023", 1500.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()); + }); + + // Disburse Loan + disburseLoan(loanId, BigDecimal.valueOf(1000.00), "01 March 2023"); + + // verify repayment schedule + verifyRepaymentSchedule(loanId, // + installment(0, null, "01 March 2023"), // + installment(250.0, false, "01 March 2023"), // + installment(250.0, false, "16 March 2023"), // + installment(250.0, false, "31 March 2023"), // + installment(250.0, false, "15 April 2023")// + ); + + // make down payment + final PostLoansLoanIdTransactionsResponse downPaymentTransaction_1 = loanTransactionHelper.makeLoanDownPayment(loanId, + new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy").transactionDate("01 March 2023").locale("en") + .transactionAmount(250.0)); + assertNotNull(downPaymentTransaction_1); + + // chargeback down payment transaction + final Long chargebackTransactionId = loanTransactionHelper.applyChargebackTransaction(loanId.intValue(), + downPaymentTransaction_1.getResourceId(), "50.00", 0, responseSpec); + + reviewLoanTransactionRelations(loanId.intValue(), downPaymentTransaction_1.getResourceId(), 1, Double.valueOf("750.00")); + reviewLoanTransactionRelations(loanId.intValue(), chargebackTransactionId, 0, Double.valueOf("800.00")); + + // verify repayment schedule + verifyRepaymentSchedule(loanId, // + installment(0, null, "01 March 2023"), // + installment(250.0, true, "01 March 2023"), // + installment(300.0, false, "16 March 2023"), // + installment(250.0, false, "31 March 2023"), // + installment(250.0, false, "15 April 2023")// + ); + + // verify journal entries for chargeback transaction + GetJournalEntriesTransactionIdResponse journalEntries = journalEntryHelper + .getJournalEntries("L" + chargebackTransactionId.toString()); + assertEquals(2L, journalEntries.getTotalFilteredRecords()); + assertEquals(50.0, journalEntries.getPageItems().get(0).getAmount()); + assertEquals("CREDIT", journalEntries.getPageItems().get(0).getEntryType().getValue()); + + assertEquals(50.0, journalEntries.getPageItems().get(1).getAmount()); + assertEquals("DEBIT", journalEntries.getPageItems().get(1).getEntryType().getValue()); + + }); + } + + private Long createLoanProductWithMultiDisbursalAndRepaymentsWithEnableDownPayment(boolean isAdvancedPaymentStrategy) { + boolean multiDisburseEnabled = true; + PostLoanProductsRequest product = isAdvancedPaymentStrategy + ? createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() + : createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct(); + product.setMultiDisburseLoan(multiDisburseEnabled); + product.setNumberOfRepayments(3); + product.setRepaymentEvery(15); + + if (!multiDisburseEnabled) { + product.disallowExpectedDisbursements(null); + product.setAllowApprovedDisbursedAmountsOverApplied(null); + product.overAppliedCalculationType(null); + product.overAppliedNumber(null); + } + + product.setEnableDownPayment(true); + product.setDisbursedAmountPercentageForDownPayment(DOWN_PAYMENT_PERCENTAGE); + product.setEnableAutoRepaymentForDownPayment(false); + + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + GetLoanProductsProductIdResponse getLoanProductsProductIdResponse = loanProductHelper + .retrieveLoanProductById(loanProductResponse.getResourceId()); + assertNotNull(getLoanProductsProductIdResponse); + return loanProductResponse.getResourceId(); + + } + + private void reviewLoanTransactionRelations(final Integer loanId, final Long transactionId, final Integer expectedSize, + final Double outstandingBalance) { + + GetLoansLoanIdTransactionsTransactionIdResponse getLoansTransactionResponse = loanTransactionHelper.getLoanTransaction(loanId, + transactionId.intValue()); + assertNotNull(getLoansTransactionResponse); + assertNotNull(getLoansTransactionResponse.getTransactionRelations()); + assertEquals(expectedSize, getLoansTransactionResponse.getTransactionRelations().size()); + // Outstanding amount + assertEquals(outstandingBalance, getLoansTransactionResponse.getOutstandingLoanBalance()); + } +}